Saltar al contenido
AldeaCode Logo
Generador Slugs / Django Generadores 100% local

Generar slugs en Django: SlugField, slugify y unicidad

Django trae todo lo que necesitas para URLs limpias sin paquetes externos: un campo de modelo, una función utilitaria, un helper de admin y un modo Unicode. Las piezas encajan si las cableas en el orden correcto.

SlugField es el tipo de columna, slugify es la función

En tu modelo, guarda el slug en un models.SlugField(max_length=140). Es un VARCHAR con validación de base de datos que comprueba que el valor cumple la regex de slug (letras, dígitos, guiones, guiones bajos). No genera el slug por ti. Eso es un paso aparte.

Para convertir un título en string de slug, llama a django.utils.text.slugify:

from django.utils.text import slugify
slugify("Hola, mundo! 2026")  # "hola-mundo-2026"

slugify baja a minúsculas, quita lo que no es palabra, colapsa espacios en un único guion y recorta los bordes. Es determinista y se puede llamar tantas veces como quieras.

allow_unicode para títulos no ASCII

Por defecto slugify pasa por unicodedata.normalize('NFKD', ...) y descarta lo que no sea ASCII. Un título como "Cómo crear" sale "como-crear", lo cual está bien en español, pero se pierde información en griego, cirílico, árabe, chino.

Si quieres conservar los caracteres originales, pasa allow_unicode=True:

slugify("Καλημέρα κόσμε", allow_unicode=True)  # "καλημέρα-κόσμε"

Combínalo con SlugField(allow_unicode=True) para que el validador del campo acepte el mismo conjunto de caracteres que produce la función. Si el campo del modelo es solo ASCII y escribes un slug Unicode, salta error de validación en full_clean o al guardar en el admin.

prepopulated_fields en admin y la trampa del auto-update

En ModelAdmin, define prepopulated_fields = {"slug": ("title",)} y el formulario del admin rellenará el campo de slug con JavaScript a medida que escribes el título. Usa un port de slugify en el navegador, así que el resultado coincide con el del servidor.

La trampa: la prepopulación solo dispara mientras el campo de slug está vacío. Una vez guardas la fila el JS deja de tocarlo, que es lo correcto para contenido publicado. Si quieres que el slug siga al título para siempre, tienes que sobreescribir save y recalcular, pero hacer eso en un post publicado rompe todos los enlaces entrantes. La mayoría de apps tipo CMS bloquean el slug tras el primer guardado y aceptan que retitular implica edición manual de slug más una redirección.

Unicidad con unique_for_date y override de save

SlugField(unique=True) pone una restricción a nivel de base de datos y rechaza duplicados. Es lo que la mayoría de blogs quieren. Si permites el mismo slug en fechas distintas (un blog de status diario, por ejemplo) usa unique_for_date="published_at" para acotar la unicidad a un día concreto.

Para el comportamiento típico de "añade un número si choca", sobreescribe save:

def save(self, *args, **kwargs):
    if not self.slug:
        base = slugify(self.title, allow_unicode=True)
        candidate = base
        n = 1
        while Post.objects.filter(slug=candidate).exclude(pk=self.pk).exists():
            n += 1
            candidate = f"{base}-{n}"
        self.slug = candidate
    super().save(*args, **kwargs)

Este patrón vale a volumen moderado. Con concurrencia muy alta hay carrera entre el SELECT y el INSERT y acabas con violación de unicidad; envuélvelo en try/except de IntegrityError con reintento, o usa una columna de secuencia.

Ejemplo completo

python
# blog/models.py
from django.db import models
from django.utils.text import slugify


class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=140, unique=True, allow_unicode=True)
    body = models.TextField()
    published_at = models.DateTimeField(null=True, blank=True)

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title, allow_unicode=True)
        super().save(*args, **kwargs)


# blog/admin.py
from django.contrib import admin
from .models import Post


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("title",)}
    list_display = ("title", "slug", "published_at")

¿Solo necesitas el resultado?

Cuando estás planificando URLs en un calendario editorial antes de que el post viva en Django, el generador de slugs en navegador de aldeacode.com te da el mismo resultado que django.utils.text.slugify, corre entero en tu máquina, y te deja planificar un mes de slugs en una hoja de cálculo sin abrir un shell.

Abrir Generador de Slugs para URLs →

Preguntas frecuentes

¿Dejo que un signal actualice el slug automáticamente?

No. Un post_save que recalcula el slug cada vez que editas el título es la receta para URLs rotas. Calcula el slug en el primer guardado (o en el admin vía prepopulated_fields) y solo tócalo después con una migración explícita y redirecciones preparadas.

¿Por qué para algunos títulos el slug sale vacío?

slugify quita los caracteres que no tienen plegado ASCII. Un título compuesto solo de emojis o de signos slugifica a string vacío. Pon un fallback: self.slug = slugify(self.title) or f'post-{self.pk}'. Ejecútalo después del primer save para tener pk poblado.

¿Puedo usar slugify fuera de un modelo, en un script o una vista?

Sí. django.utils.text.slugify es una función plana sin dependencia de settings. La puedes llamar desde management commands, tareas de Celery, serializers de DRF, donde sea. allow_unicode es el único parámetro que la mayoría de código necesita.