Абсолютные пути URL в Django

В одной из предыдущих заметок, нами был написан шаблон для вывода списка публикаций на главной странице. Ссылка на полный текст публикации имеет вид

{% url 'articles:detail' post.id %},

что позволяет получить определённую страницу по её идентификатору (id). За отображение страницы ответственность несёт представление articles из пространства имён (namespace) detail. В данном ниже примере для разнообразия используем другую модель, а также для упрощения понимания материала откажемся от использования пространств имён: одно приложение — одна модель — один urls.py.

И всё бы хорошо, но ссылки подобного вида иногда могут не подойти вам. Вот пара простых примеров, иллюстрирующих этот тезис:

  • Вам нужно получить статью по slug
  • Вам нужно отобразить ссылки на предыдущую и следующую статью

Что же, разберём указанные примеры по-порядку.

Вид urls.py

Создание файла urls.py, отвечающего нашим запросам. Отображено использование обобщённых представлений, подробности о которых вы без труда найдёте в документации.

У нас имеется представление IndexView, на которое возложен вывод индексной страницы со списком последних публикаций, а также DetailView, которое выводит отдельную запись. В шаблоне к ним можно обращаться как к latest_articles_list и detail соответственно.

# urls.py

from django.conf.urls import patterns, include, url
from .views import IndexView, DetailView


urlpatterns = patterns('',
    url(r'^$', IndexView.as_view(), name='latest_articles_list'),
    url(r'^articles/(?P<slug>[-\w]+)/$', DetailView.as_view(template_name = 'article.html'), name='detail'),
)

Обращение по первичному ключу

В случае, когда нужно получить объект по slug, нам важно знать, что любая модель имеет свой первичный ключ (primary key) и его можно переопределить. По-умолчанию первичным ключом является поле id, генерируемое автоматически. Таким образом, мы получаем URL вида:

http://site.org/articles/1/
http://site.org/articles/2/

А хотим видеть такие адреса:

http://site.org/articles/krasivyi-url/
http://site.org/articles/po-poly-slug/

Самым простым решением является переопределение первичного ключа, но в этом случае вы больше не сможете обращаться к полю модели по id.

# models.py

from django.db import models


class Article(models.Model):
    ''' Create Post '''
    title = models.CharField(max_length=150, verbose_name='Title')
    content = models.TextField()
    pub_date = models.DateField(auto_now=True, verbose_name='Create Date')
    update = models.DateField(auto_now=False, blank=True, null=True,verbose_name='Last Update')
    author = models.ForeignKey(to=User, verbose_name='Author')
    slug = models.SlugField(primary_key=True, max_length=250, unique=True)
    summary = models.TextField(blank=True, max_length=250, help_text='Meta Description')

Как мог заметить наблюдательный читатель, мы задали поле slug со значением primary_key=True. Предполагается, что в этом поле будет установлен устраивающий автора красивый URL. Теперь следует ссылаться на детальное представление публикации именно по этому полю. Конкретный пример будет приведён чуть ниже.

Примечание

Если при создании представления вы используете обобщённые представления, вернуть публикацию по полю slug, не нагромождая ваше представление и не затрагивая primary key, также может быть очень легко. В этом вам посодействуют DetailView и get_object_or_404

# views.py

from django.shortcuts import get_object_or_404
from django.views.generic.detail import DetailView

class DetailView(DetailView):
    """ Return detail data """
    model = Article
    context_object_name = 'article'

    def get_object(self):
        return get_object_or_404(Article, slug__iexact=self.kwargs['slug'])

Прежде, чем приступить к написанию шаблонов, решим второй вопрос: о получении абсолютных путей к объекту.

Абсолютные пути

Итак, для примера мы решили, что будем выводить предыдущую и следующую статью при обращении к статье текущей. Если просто обращаться к статье по указанному ключу, можно поиметь некоторые проблемы. Наш URL имеет относительный путь, поэтому при наведении на любую ссылку внутри поста, которая сформирована также (то есть с указанием {%raw%}{% url 'view' object.slug %}{%endraw%}), первичный ключ выбранной статьи просто будет добавлен к уже имеющемуся адресу. Вот так:

имеем:
http://site.org/articles/my-article

при наведении на другую ссылку получим:
http://site.org/articles/my-article/my-next-article

Поэтому мы добавим к нужной модели спасательный круг в виде метода get_absolute_url и сразу же выведем в шаблоне именно то, что нужно.

# models.py

from django.db import models
from django.core.urlresolvers import reverse


class Article(models.Model):
    title = models.CharField(max_length=150, verbose_name='Title')
    content = models.TextField()
    ...

    def get_absolute_url(self):
        return reverse('detail', args=[str(self.slug)])

Подготовка шаблонов

Теперь в шаблонах мы можем ссылаться на статью по её абсолютному адресу, используя get_absolute_url. Примеры:

подобным образом может выглядеть индексная страница со списком последних публикаций

<!--index.html-->
{% extends "base.html" %}

{% block content %}
    <main>
        {% for article in latest_articles_list %}
            <a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
        {% endfor %}
    </main>
{% endblock content %}

а на странице детального представления можно добавить ссылки на предыдущий и следующий пост

<!--article.html-->
{% extends "base.html" %}

{% block content %}
<article>
    <h2><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></h2>
    {{ article.content }}
    <div class="links">
        <a href="{{ article.get_next_by_pub_date.get_absolute_url }}">Next</a>
        <a href="#">on top</a>
        <a href="{{ article.get_previous_by_pub_date.get_absolute_url }}">Prev</a>
    </div>
</article>
{% endblock %}

Model.get_next_by_pub_date вернёт следующую публикацию, основываясь на дате её создания

Model.get_previous_by_pub_date отобразит предыдущую публикацию.

Применив к ним метод get_absolute_url, мы получим искомое решение.