Last active
February 12, 2026 17:53
-
-
Save dryan/0f8f1a7d3f2d4b62584d5c5ab8d10347 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from django import http | |
| from django.db.models import F | |
| from app.models import Redirect | |
| class RedirectsMiddleware: | |
| def __init__(self, get_response): | |
| self.get_response = get_response | |
| def __call__(self, request: http.HttpRequest) -> http.HttpResponse: | |
| response = self.get_response(request) | |
| if response.status_code == 404: | |
| redirect = Redirect.objects.filter(path=request.path).first() | |
| if redirect: | |
| Redirect.objects.filter(pk=redirect.pk).update(used=F("used") + 1) | |
| return http.HttpResponseRedirect(redirect.destination) | |
| return response |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import typing | |
| from django.contrib import admin | |
| from django.core.files.images import ImageFile | |
| from django.db import models | |
| from django.utils.translation import gettext_lazy as _ | |
| class RedirectableMixin(models.Model): | |
| previous_url = models.CharField(max_length=255, editable=False, default="") | |
| class Meta: | |
| abstract = True | |
| def save(self, *args, **kwargs): | |
| try: | |
| url = self.get_absolute_url() | |
| except AttributeError: | |
| url = None | |
| if url and not url == self.previous_url: | |
| from app.models import Redirect | |
| redirect = Redirect.objects.filter(path=self.previous_url).first() | |
| if redirect: | |
| redirect.destination = url | |
| redirect.is_system = True | |
| redirect.save() | |
| else: | |
| Redirect.objects.create( | |
| path=self.previous_url, destination=url, is_system=True | |
| ) | |
| Redirect.objects.filter(destination=self.previous_url).update( | |
| destination=url | |
| ) | |
| Redirect.objects.filter(path=url).delete() | |
| self.previous_url = url | |
| return super().save(*args, **kwargs) | |
| class SlugMixin(models.Model): | |
| slug = models.SlugField( | |
| max_length=255, | |
| blank=True, | |
| default="", | |
| ) | |
| slug_source = "name" | |
| def save(self, *args, **kwargs): | |
| if hasattr(self, self.slug_source) and not self.slug: | |
| self.slug = slugify(getattr(self, self.slug_source)) | |
| count = ( | |
| self._meta.model.objects.filter(slug__iexact=self.slug) | |
| .exclude(pk=self.pk) | |
| .count() | |
| ) | |
| if count: | |
| self.slug = f"{self.slug}-{count + 1}" | |
| if self.slug: | |
| self.slug = self.slug.lower() | |
| return super().save(*args, **kwargs) | |
| class Meta: | |
| abstract = True |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from django.core.exceptions import ValidationError | |
| from django.core.validators import RegexValidator | |
| from django.core.validators import URLValidator | |
| from django.db import models | |
| from django.utils.translation import gettext_lazy as _ | |
| path_validator = RegexValidator(r"^/", _("Path must start with /")) | |
| def validate_path_or_url(value: str) -> None: | |
| err = None | |
| for validator in [path_validator, URLValidator()]: | |
| try: | |
| validator(value) | |
| return True | |
| except ValidationError as exc: | |
| err = exc | |
| raise err | |
| class Redirect(models.Model): | |
| path = models.CharField( | |
| _("path"), | |
| max_length=255, | |
| validators=[path_validator], | |
| db_index=True, | |
| help_text=_("must start with a /"), | |
| ) | |
| destination = models.CharField( | |
| _("destination"), | |
| max_length=255, | |
| validators=[validate_path_or_url], | |
| help_text=_( | |
| "must start with a / or be a full URL with http:// or https:// " | |
| "at the beginning." | |
| ), | |
| ) | |
| is_system = models.BooleanField( | |
| _("system managed"), | |
| default=False, | |
| editable=False, | |
| ) | |
| used = models.PositiveBigIntegerField( | |
| _("times used"), | |
| default=0, | |
| editable=False, | |
| ) | |
| class Meta: | |
| ordering = ["path"] | |
| def __str__(self): | |
| return f"{self.path} → {self.destination}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment