Django es el entorno de desarrollo web para perfeccionistas con límites de tiempo

Entradas sobre "urls":

Trabajar con slugs en URLs

Una de las cosas que los buscadores tienen en cuenta para establecer la relevancia de sus resultados son las palabras que aparecen en las URLs. Por eso conviene que las URLs de nuestros objetos incluyan un slug que los represente.

Un slug es una cadena sin caracteres especiales, en minúsculas y con guiones en lugar de espacios, óptima para ser utilizada en URLs. Con un slug podríamos hacer que una URL tipo /negocios/33/ sea /negocios/mi-negocio/. En este caso el slug sería mi-negocio.

Django tiene un campo SlugField que podemos usar en nuestros modelos precisamente para eso. Si tenemos un campo slug que es único la siguiente URL nos sirve como URL del objeto:

url(r'^negocio/(?P<slug>[-\w]+)/$', mi_vista, name='mi_vista')

Si el slug no es único podemos utilizar la combinación de id y slug. Conviene que el slug aparezca primero ya que los buscadores dan más relevancia a las palabras que aparecen antes en la propia URL:

url(r'^negocio/(?P<slug>[-\w]+)/(?P<id>\d+)/$', mi_vista, name='mi_vista')

Si el slug tiene que generarse dinámicamente de forma transparente para el usuario podemos hacerlo en el método save() del modelo. Podemos utilizar el filtro slugify de django...

from django.db import models
from django.template import defaultfilters

class Negocio(models.Model):
    nombre = models.CharField(max_length=100)
    slug = models.SlugField(max_length=100)

    def save(self, *args, **kwargs):
        self.slug = defaultfilters.slugify(self.nombre)
        super(Negocio, self).save(*args, **kwargs)

...o bien slughifi que es más cuidadoso al generar slugs transformando caracteres acentuados en sus respectivos caracteres sin acentos.

from slughifi import slughifi
# ...
def save(self, *args, **kwargs):
    self.slug = slughifi(self.nombre)
    super(Negocio, self).save(*args, **kwargs)

Generar el slug dinámicamente de forma transparente para el usuario tiene un problema: El slug puede variar y por lo tanto la URL puede cambiar. Podemos aplicar dos soluciones:

  1. Generar el slug sólo la primera vez que se guarda el objeto:

    def save(self, *args, **kwargs):
        if not self.id:
            self.slug = slughifi(self.nombre)
        super(Negocio, self).save(*args, **kwargs)
    

    La ventaja es que el slug se mantendrá aunque el nombre cambie. La desventaja que el nombre y el slug pueden acabar teniendo poco que ver el uno con el otro.

  2. Regenerar el slug cada vez que se actualice el objeto pero ofrecer la posibilidad de que las URLs antiguas sigan funcionando. Podemos conseguirlo haciendo lo siguiente en nuestra vista:

    from django.http import HttpResponsePermanentRedirect
    from django.shortcuts import get_object_or_404
    
    def mi_vista(request, slug, id):
        try:
            negocio = Negocio.objects.get(slug=slug, id=id)
        except ObjectDoesNotExist:
            negocio = get_object_or_404(Negocio, id=id)
            return HttpResponsePermanentRedirect(negocio.get_absolute_url())
        # ...
    

    De esta forma el slug podrá cambiar si hace falta y para cualquier enlace que exista hacia la URL antigua se devolverá una redirección permanente a la nueva URL.

Publicado por Antonio Melé el Domingo 29 de Agosto de 2010 | 1 comentario | Categorías: trucos, urls

Subdominios con Django

En ocasiones nos interesa trabajar con subdominios en nuestros proyectos Django. Para ello podemos utilizar un sencillo middleware para subdominios que podemos encontrar en djangosnippets. Basta con guardarlo en nuestro proyecto e incluirlo en la lista MIDDLEWARE_CLASSES de nuestro settings.py. De esta forma tendremos el subdominio de la petición en la variable subdomain del objeto request.

El problema al trabajar con subdominios es que las sesiones iniciadas en un subdominio no se mantienen cuando se cambia a otro. Para poder mantener activas las sesiones entre subdominios tenemos que añadir la siguiente línea al settings.py de nuestro proyecto:

SESSION_COOKIE_DOMAIN = '.mi-dominio.com'

De esta forma la cookie de sesión que se almacena en el navegador valdrá para todos los subdominios.

Subdominios para usuarios

Si por ejemplo queremos dar un subdominio a cada usuario de nuestro proyecto podemos modificar el middleware para que busque el usuario adecuado y lo incluya en el objeto request. El middleware quedaría de la siguiente manera:

from django.http import Http404
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User

class SubdomainMiddleware:
    """ Make the subdomain publicly available to classes """

    def process_request(self, request):
        domain_parts = request.get_host().split('.')
        if (len(domain_parts) > 2):
            subdomain = domain_parts[0]
            if (subdomain.lower() == 'www'):
                subdomain = ''
            domain = '.'.join(domain_parts[1:])
        else:
            subdomain = ''
            domain = request.META['HTTP_HOST']

        request.subdomain = subdomain
        request.domain = domain

        if subdomain != 'www' and subdomain != '':
            # Buscamos el usuario del subdominio
            try:
                request.usuario_subdominio = User.objects.filter(username=subdomain)
            except ObjectDoesNotExist:
                raise Http404

De esta forma el usuario correspondiente al subdominio que se visita está disponible en la variable usuario_subdominio del objeto request. Si utilizamos variables de contexto y tenemos 'django.core.context_processors.request' en la lista de procesadores de contexto (setting TEMPLATE_CONTEXT_PROCESSORS) podemos obtener el usuario actual en nuestras plantillas mediante:

{{ request.usuario_subdominio }}

Publicado por Antonio Melé el Domingo 2 de Agosto de 2009 | 6 comentarios | Categorías: middleware, snippets, trucos, urls

Idiomas en nuestras URLs gracias a django-localeurl

django-localeurl es una aplicación que permite el uso de códigos de idiomas en las URLs de nuestro proyecto. Esto trae la ventaja de que cada página en cada idioma tiene una URL propia. Gracias a ello los buscadores indexan cada página en los distintos idiomas disponibles y nuestros usuarios pueden compartir las URLs de una página en el idioma que deseen. Además funciona a la perfección con el LocaleMiddleware que viene con Django y que sirve para hacer la "negociación" para descubrir el idioma del usuario en función de si ya ha seleccionado un idioma previamente, si por el contrario debe utilizar el idioma de su navegador o como último recurso el idioma por defecto de nuestro proyecto.

django-localeurl nos permitirá cambiar nuestras URLs de http://mi-proyecto.com/noticias a URLs del tipo http://mi-proyecto.com/es/noticias, http://mi-proyecto.com/de/noticias, etc. No hará falta cambiar nada en nuestra URLconf para hacerlo funcionar.

Instalar django-localeurl

  1. Descargamos el paquete localeurl del sitio oficial a nuestro path de Python.

  2. Añadimos 'localeurl.middleware.LocaleURLMiddleware' al setting MIDDLEWARE_CLASSES por encima de 'django.middleware.common.CommonMiddleware'.

    Importante: También deberemos situarlo por debajo de 'django.middleware.locale.LocaleMiddleware' en caso de que utilicemos LocaleMiddleware, para que éste pueda realizar la negociación de selección de idioma previamente.

  3. Añadimos 'localeurl' a nuestras aplicaciones instaladas (setting INSTALLED_APPS).

  4. Nos aseguramos de que el valor del setting LANGUAGE_CODE esté en el setting LANGUAGES que contiene la lista de lenguajes disponibles para nuestro proyecto. Podemos visualizar la lista de lenguajes por defecto en el código fuente de django.conf.global_settings.

Configurar django-localeurl

Existen 3 settings que nos permiten personalizar el funcionamiento de django-localeurl: LOCALE_INDEPENDENT_PATHS, REDIRECT_LOCALE_INDEPENDENT_PATHS y PREFIX_DEFAULT_LANGUAGE.

LOCALE_INDEPENDENT_PATHS

Nos permite especificar para qué URLs no deseamos que se añada el código de lenguaje. Deberemos añadirlas como expresiones regulares. Un ejemplo:

import re
LOCALE_INDEPENDENT_PATHS = (
    re.compile('^/aviso-legal/'),
    re.compile('^/privacidad/'),
)

REDIRECT_LOCALE_INDEPENDENT_PATHS

Por defecto es False. Nos permite especificar si al acceder incluyendo un código de lenguaje a alguna de las URLs especificadas en REDIRECT_LOCALE_INDEPENDENT_PATHS se debe redirigir a la misma sin código de lenguaje. Por ejemplo si al acceder a /de/privacidad/ se debe redirigir a /privacidad/.

PREFIX_DEFAULT_LANGUAGE

Por defecto es True. Nos permite especificar si para el lenguaje por defecto se añade el código de idioma a la URL. Por ejemplo, si nuestro idioma por defecto es el español (LANGUAGE_CODE = 'es') y PREFIX_DEFAULT_LANGUAGE es True, la URL /noticias/ cargará directamente la vista apropiada y /noticias/es/ será redirigida a /noticias/.

Uso en plantillas

Cualquier aplicación que utilice etiquetas {% url %} para las URLs en sus plantillas funcionará correctamente con django-localeurl. A parte de esto django-localeurl incluye un template tag y 2 filtros que nos aportan flexibilidad a la hora de utilizar URLs en nuestras plantillas.

Para hacerlos funcionar debemos cargarlos con load en las plantillas en las que queramos trabajar con ellos:

{% load localeurl_tags %}

El tag {% locale_url %} funciona del mismo modo que el concido {% url %} pero permitiéndonos especificar el idioma que debe utilizarse en la URL. Nos sirve para enlaces que por algún motivo queramos mostrar en un idioma concreto. Para ello si tenemos algo como:

<a href="{% url ver-articulo articulo.id %}">Ver artículo</a>

Para que el enlace apunte a un idioma concreto es tan sencillo como utilizar:

<a href="{% locale_url "en" ver-articulo articulo.id %}">Ver artículo en inglés</a>

Los filtros incluídos son chlocale y rmlocale. chlocale nos permite añadir o cambiar el código de lenguaje de una URL y rmlocale eliminar el código de lenguaje de una URL. Un ejemplo de uso:

<p>Haz <a href="{{ mi_url|chlocale:"de" }}">click aquí</a> para cambiar a alemán.</p>
<p>La url {{ otra_url }} sin código de lenguaje es {{ otra_url|rmlocale }}</p>

Publicado por Antonio Melé el Martes 30 de Diciembre de 2008 | 5 comentarios | Categorías: aplicaciones, internacionalización, plantillas, urls