URL всех родителей страницы в Django. Как и зачем?

Дата публикации:29 апреля 2013 г. 23:49:12

Здравствуйте! Если вы читали мои предыдущие посты на тему создания своей CMS с использованием Django, то могли заметить, что урлы в страницах выглядят примерно так:

 

/page/parent1/parent2/some_page/

 

Поясню зачем это нужно. Как известно в среде сеошников, даже URL страницы иногда оказывает существенное влияние на поисковые системы и позволяет подниматься в поиске выше конкурентов, если содержит в себе ключевые слова для поиска. К примеру, вы написали статью, которая помогает «прокачивать» ваш сайт по семантическому ядру вашего сайта. Статья содержит в себе ключевые запросы по данной теме, теги H1 и H2 и т.д. Что можно еще добавить для улучшения показателей? Ответ очевиден - URL страницы.

 

Теперь расскажу, как это сделать в Django. Как вы уже наверное знаете, Django не очень любит русские символы в строке запроса, следовательно нужно обходиться латиницей. Создаем поле slug типа SlugFiield и в классе страницы пишем:

prepopulated_fields = {
    'slug': ('title',)
}

, где title - заголовок вашей страницы. Таким образом урл будет приводится к латинице. Поисковики и латиницу прекрасно прочтут. Хотя, я не сеошник и всех таинств этой профессии не познавал.

Дальше, нам необходимо определить в классе модели метод get_absolute_url() и внутрь него добавить следующее:

def get_absolute_url(self):
    if self.parent is None:
        return '/page/%s/' % self.slug
    if self.homepage:
        return '/'
    url = get_all_ancestors(self.parent, self.slug)
    return '/page%s' % url
get_absolute_url.short_description = _('URL')

В моем случае, есть поле parent, которое имеет тип TreeForeignKey из mptt. А также, тип BooleanField поля homepage, которое возвращает главную страницу сайта. Это удобно. Теперь осталось разобраться с get_all_ancestors(). Это функция, которая имеет вид:

def get_all_ancestors(parent, url = ''):
    url = '%s/%s' % (parent.slug, url)
    if parent.parent:
        return get_all_ancestors(parent.parent, url)
    else:
        return '/%s/' % url

Как видно, это рекурсивная функция, которая последовательно обходит всех родителей и возвращает конечный URL. Конечно, это очень расточительно, так как каждый вызов функции - это проблема производительности, и при этом каждый раз выполняется по одному SELECT запросу. Это уже не так страшно из-за кеширования, но все же. Я выбрал данный способ, потому что он наиболее очевиден. Возможно, будет лучше сделать один запрос в базу и вернуть все дерево, затем уже работать с данными. Это тоже можно сделать, но мне лень. Если у вас намечается высоконагруженный проект, то лучше вам поступить именно так :) Ах, да. Не забудьте добавить в класс Meta:

unique_together = ('parent', 'slug')

И в метод clean():

if Page.objects.filter(slug = self.slug, parent = None).exclude(
        id = self.id).exists() and self.parent is None:
            raise ValidationError(
                _('Record with this slug already exists')
            )

Это для того, чтобы избежать повторных slug без родителей. И метод save() добавить следующее для того, чтобы предотвратить создание двух домашних страниц:

def save(self, *args, **kwargs):
    if self.homepage is True:
        try:
            page = Page.objects.get(homepage = True)
            page.homepage = False
            page.save()
        except ObjectDoesNotExist:
            self.homepage = True
    return super(Page, self).save(*args, **kwargs)

А urls.py будет иметь вид:

urlpatterns = patterns('',
    url(r'^$', 'ваше представление index'),
    url(r'^page/[a-z0-9-_]+', 'ваше представление page'),
)

И views.py:

def index(request):
    try:
        page = Page.objects.get(homepage = True)
    except ObjectDoesNotExist:
        raise Http404
    return render_to_response(
        'index.html',
        {'page': page},
        context_instance = RequestContext(request)
    )

def page(request):
    try:
        path = request.path.lstrip('page')
        path = path.strip('/').split('/')
        page = Page.objects.get(
            slug = path[-1], homepage = False
        ) or Page.objects.get(
            slug = path[-1],
            parent__slug = path[-2],
            homepage = False
        )
    except ObjectDoesNotExist:
        raise Http404
    return render_to_response(
        'page.html',
        {'page': page},
        context_instance = RequestContext(request)
    )

Как видно, path разбивает строку запроса и находит страницу с parent.slug равным предпоследнему в списке и slug страницы равным последнему в списке. Пожалуй, это все. Спасибо за внимание! Удачи вам в ваших проектах!

Метки:python, django, seo