Skip to content

Instantly share code, notes, and snippets.

@dryan
Last active February 12, 2026 17:53
Show Gist options
  • Select an option

  • Save dryan/0f8f1a7d3f2d4b62584d5c5ab8d10347 to your computer and use it in GitHub Desktop.

Select an option

Save dryan/0f8f1a7d3f2d4b62584d5c5ab8d10347 to your computer and use it in GitHub Desktop.
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
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
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