Нашли опечатку?

Выделите её мышкой и нажмите Enter

Ctrl-Enter
Выполнено:
92 1 199 25
Всего пользователей: 1248

Внимание!

Книга написана для очень старой версии Django. Надеемся автор обновит ее и мы сможешь обновить перевод.
Пока советуем читать перевод официальной документации.

Ваше третье представление: Динамические URL

В нашем представлении current_datetime содержимое страницы — текущая дата и время — были динамическими, но URL оставался статическим. В большинстве динамических web-приложений URL содержат параметры, которые влияют на генерацию страницы. Например, онлайн магазин может отображать книгу по её собственному URL, таким как /books/243/ и /books/81196/.

Давайте создадим третье представление, которое отображает текущую дату и время со смещением на указанное количество часов. Цель — реализовать функциональность, с помощью которой сайт будет по URL /time/plus/1/ отдавать текущее время сдвинутое на час вперёд, а по URL /time/plus/2/ — на два часа вперёд и так далее.

Новичок может подумать, что потребуется создавать функцию представления для каждого смещения часа, что может выразится в следующей схеме URL:

urlpatterns = patterns('',
    ('^time/$', current_datetime),
    ('^time/plus/1/$', one_hour_ahead),
    ('^time/plus/2/$', two_hours_ahead),
    ('^time/plus/3/$', three_hours_ahead),
    ('^time/plus/4/$', four_hours_ahead),
)

Очевидно, это направление мысли является ущербным. Такой подход не только приведёт к избыточности функций представления, но и к тому, что приложение будет явно ограничено заранее заданным диапазоном смещений — один, два, три или четыре часа. Если мы решим создать страницу, которая отображает текущее время со смещением на пять часов вперёд, нам потребуется создать отдельное представление и внести дополнительную строчку в схему URL, продолжая создавать избыточность. Нам потребуется немного окунуться в теорию.

Слово о красивых URL

Если вы работали с другими платформами разработки web-приложения, такими как PHP или Java, вы можете подумать: «Эй, давайте использовать параметр строки запроса!» — что-нибудь аналогичное /time/plus?hours=3, в котором смещение будет указано через параметр hours в URL.

Вы можете сделать так и с Django (и мы расскажем об этом в главе 7 FIXME), но Django пропагандирует философию использования красивых URL. URL /time/plus/3/ гораздо яснее, проще, более читаемое, его проще сказать кому-нибудь вслух и ... просто проще, чем его оппонент. Красивые URL являются характеристикой качества web-приложения.

Система схемы URL Django поощряет создание красивых URL, упрощая использование таких URL, в отличие от других.

Как же мы должны спроектировать наше приложение для обработки соответствующих смещений времени? Ответ лежит в использовании шаблонов подстановки. Как мы упоминали ранее, шаблон URL является регулярным выражением. Следовательно, мы можем использовать шаблон \d+ для выделения одной или более цифр:

urlpatterns = patterns('',
    # ...
    (r'^time/plus/\d+/$', hours_ahead),
    # ...
)

(Мы используем #..., чтобы показать, что здесь могут быть другие шаблоны, которые мы временно исключили из данного примера.)

Новый шаблон схемы URL будет соответствовать любому URL, подобному /time/plus/2/, /time/plus/25/ и даже /time/plus/100000000000/. Давайте ограничим смещение 99-ю часами. Это означает, что мы разрешаем только одно- или двухзначиные числа. На языке регулярных выражений это преобразовывается в \d{1,2}:

(r'^time/plus/\d{1,2}/$', hours_ahead),

Замечание

При разработке web-приложения, важно всегда рассматривать наиболее диковинные варианты ввода данных и решать должно или нет приложение обрабатывать такой ввод. Мы ограничили здесь возможности ввода смещения 99-ю часами.

Ещё одной важной деталью, которую мы ввели здесь, является символ r в начале строки регулярного выражения. Он указывает Python, что строка является «сырой» — в её содержимом не следует интерпретировать обратные слеши. В обычной строке Python, обратные слеши используются для экранирования особых символов, например, \n — односимвольная строка. Если предварить её символом r, сделав её «сырой», Python не будет выполнять экранирование — таким образом, r'\n' станет двухсимвольной строкой, содержащей обратный слеш и символ n. Существует естественная коллизия между обычными обратными слешами и используемыми в регулярных выражениях. Крайне рекомендуется всегда использовать сырые строки при определении регулярных выражений в Python. С этого момента следует считать, что все шаблоны URL, показанные в этой книге, будут представлены сырыми строками.

После применения символов подстановки в URL необходим способ передачи выделенных подстановкой данных в функцию представления, это позволит нам использовать единственную функцию представления для любого разрешённого смещения часов. Мы осуществляем это с помощью установки скобок вокруг интересующей нас части шаблона URL. В рамках нашего примера, это будет любое число в URL, таким образом, надо расположить скобки вокруг выражения \d{1,2}, например:

(r'^time/plus/(\d{1,2})/$', hours_ahead),

Если у вас уже есть опыт работы с регулярными выражениями, то вам будет легко. Мы используем скобки для выделения данных из совпавшего текста.

В итоге схема URL будет выглядеть так:

from django.conf.urls.defaults import *
from mysite.views import hello, current_datetime, hours_ahead

urlpatterns = patterns('',
    (r'^hello/$', hello),
    (r'^time/$', current_datetime),
    (r'^time/plus/(\d{1,2})/$', hours_ahead),
)

Теперь напишем представление hours_ahead.

Порядок разработки

В данном примере мы сначала создали шаблон URL, а затем представление. Но в предыдущих примерах, мы сначала создавали представление, а затем — шаблон URL. Какой подход лучше?

Хорошо, каждый разработчик уникален.

Если вам комфортно видеть весь проект целиком, может быть вам будет проще создать схему URL для всего функционала сайта за один раз, в начале проекта, а затем реализовать каждую функцию представления. Такой подход имеет преимущество, так как предоставляет ясный список работ и, по сути, определяет требования к параметрам функций представления, которые вам потребуется реализовать.

Если вам комфортно разрабатывать проект, начиная с небольших компонентов, может быть вы предпочтёте сначала реализовать представления, а затем ассоциировать их с URL. Такой подход тоже имеет право на жизнь.

В конце концов, не важно какой из подходов лучше или хуже. Они оба правильные.

Представление hours_ahead очень похоже на представление current_datetime, которое мы написали ранее, с одним только отличием: оно принимает дополнительный аргумент — количество часов смещения. Вот его код:

from django.http import Http404, HttpResponse
import datetime

def hours_ahead(request, offset):
    try:
        offset = int(offset)
    except ValueError:
        raise Http404()
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
    return HttpResponse(html)

Рассмотрим каждую строчку этого кода:

  • Функция представления, hours_ahead, принимает два параметра: request и offset.

    • Параметр request является объектом HttpRequest, аналогичным используемому в представлениях hello и current_datetime. Мы повторим ещё раз: каждое представление всегда принимает объект HttpRequest в качестве первого аргумента.

    • Параметр offset является строкой, выделенной скобками в шаблоне URL. Например, если запрошенный URL был /time/plus/3/, тогда offset будет содержать строку '3'. Если /time/plus/21/ — строка '21'. Следует отметить, что выделенные значения всегда будут строками, не целыми, даже если строка составлена только из цифр.

      (Технически, захваченные значения всегда будут объектами Unicode, а не простыми строками Python, но пока об этом беспокоиться не следует.)

      Мы решили назвать данный параметр offset, но вы можете дать ему любое другое имя, главное, чтобы оно соответствовало требованиям языка Python для идентификаторов. Имя переменной само по себе ничего не означает. Имеет значение только то, что переменная является вторым аргументом функции, после request. (Также можно использовать ключевые параметры в схеме URL вместо позиционных. Мы расскажем об этом в главе 8 FIXME.)

  • Первое, что мы делаем в функции — вызываем int() для offset. Данная функция преобразовывает строковое значение в целое.

    Следует отметить, что Python вызывет исключение ValueError, если в процессе вызова int() данная функция не сможет осуществить конвертацию, например, для строки 'foo'. В данном примере, если мы получим ValueError, мы вызовем исключение django.http.Http404, которое, как вы можете представить, возвратит страницу с ошибкой 404 — «Страница не найдена»

    Проницательному читателю будет интересно: как мы сможем попасть в ветку с ValueError, учитывая то, что регулярное выражение в нашем шаблоне — (\d{1,2}) — принимает только цифры и, следовательно, offset всегда будет состоять из строки цифр? Ответ заключается в том, что мы и не рассчитываем на это, так как шаблон URL предоставляет простой, но достаточный уровень проверки, но мы попрежнему проверяем значение на случай, если данная функция представления будет вызвана каким-либо другим способом. Это хороший подход — реализация функций представления так, как будто им в параметрах может передаваться всё что угодно. Помните про свободное связывание?

  • На следующей строке функции мы вычисляем текущую дату и время, а затем добавляем соответствующее число к часам. Мы уже встречались с datetime.datetime.now() в представлении current_datetime. Разница лишь в том, что здесь мы выполняем арифметические операции, создав объект datetime.timedelta и прибавив его к объекту datetime.datetime. Результат операции сохраняется в переменной dt.

    Данная строка также показывает почему мы вызываем int() для offset — функция datetime.timedelta требует, чтобы параметр hours содержал целое число.

  • Затем мы создаём HTML документ для данного представления, аналогично тому, как это делалось в представлении current_datetime. Есть небольшое отличие в этой строке, от предыдущей — здесь используется строка форматирования с двумя подстановками, а не одной. Следовательно, строка содержит две подстановки %s и кортеж, содержащий значения: (offset, dt)..

  • Наконец, мы возвращаем HttpResponse. Вот и всё.

Реализовав данную функцию представления и привязав её к URL, запустите тестовый сервер Django (если он ещё не запущен) и посетите http://127.0.0.1:8000/time/plus/3/ для проверки работы функции. Затем попробуйте http://127.0.0.1:8000/time/plus/5/. Затем http://127.0.0.1:8000/time/plus/24/. Наконец, посетите http://127.0.0.1:8000/time/plus/100/, чтобы проверить, что шаблон URL принимает только одно- или двухсимвольные числа. Django должна отобразить «Страница не найдена» в этом случае. http://127.0.0.1:8000/time/plus/ (без указания часов) также должен вызвать ошибку 404.

GoldenCopywr
GoldenCopywr 1 год, 8 месяцев прошло
Ответ | Ссылка

Хм, переменная offset похоже получает свое текущее значение, берущееся из урла, в конструкции:
(r'^time/plus/(\d{1,2})/$', hours_ahead)
из дефолтной переменной рег.ф-ции, сходной с $_ в других языках (что отнюдь не очевидно - ни логически, ни по тексту описания)...

alerion
alerion 1 год, 8 месяцев прошло
Ответ | Ссылка

> из дефолтной переменной рег.ф-ции, сходной с $_ в других языках

Это что за "_$" сходная в других языках?

> Параметр offset является строкой, выделенной скобками в шаблоне URL

Читайте внимательно.

alexiustx
alexiustx 1 год, 3 месяца прошло
Ответ | Ссылка

Почему-то при вводе адреса http://127.0.0.1:8000/time/plus/3/ и др. выводит ошибку:
hours_ahead() takes exactly 2 arguments (1 given)

alexiustx
alexiustx 1 год, 3 месяца прошло
Ответ | Ссылка

Тьфу, подвела невнимательность )))

vik.contact.ua
vik.contact.ua 1 год прошло
Ответ | Ссылка

Не могу найти причину...
Exception Type: SyntaxError
Exception Value:
invalid syntax (views.py, line 13)
Exception Location: ../mysite/urls.py in <module>, line 2
line 13: "def hours_ahead(request, offset):"
line 2:"from mysite.views import hello, current_datetime, hours_ahead"

rad
rad 1 год прошло
Ответ | Ссылка

Ответ на vik.contact.ua
Не могу найти причину...
Exception Type: SyntaxError
Exception Value:
invalid syntax (views.py, line 13)
Exception Location: ../mysite/urls.py in <module>, line 2
line 13: "def hours_ahead(request, offset):"
line 2:"from mysite.views import hello, current_datetime, hours_ahead"

Для вопросов есть форум.

Строка "invalid syntax (views.py, line 13)" ни о чём не говорит?

bsod_keks
bsod_keks 8 месяцев, 2 недели прошло
Ответ | Ссылка

Ответ на alexiustx
Почему-то при вводе адреса http://127.0.0.1:8000/time/plus/3/ и др. выводит ошибку:
hours_ahead() takes exactly 2 arguments (1 given)

меня до сих пор подводит. что не так???? найти не могу

apelsinsd
apelsinsd 7 месяцев, 3 недели прошло
Ответ | Ссылка

Ответ на bsod_keks
меня до сих пор подводит. что не так???? найти не могу

Функция hours_ahead () принимает ровно 2 аргумента (1 указано) = дословный перевод :-) Не передан второй аргумент.

Nule
Nule 2 месяца, 2 недели прошло
Ответ | Ссылка

Ответ на GoldenCopywr
Хм, переменная offset похоже получает свое текущее значение, берущееся из урла, в конструкции:
(r'^time/plus/(\d{1,2})/$', hours_ahead)
из дефолтной переменной рег.ф-ции, сходной с $_ в других языках (что отнюдь не очевидно - ни логически, ни по тексту описания)...

Полностью согласен. Момент важнейший, а совершенно непонятно. Фраза "Параметр offset является строкой, выделенной скобками в шаблоне URL" ясности не вносит, ибо в примере 6 пар скобок, и принцип выбора скобок опять же непонятен.

djoa
djoa 2 месяца, 1 неделя прошло
Ответ | Ссылка

Ответ на Nule
Полностью согласен. Момент важнейший, а совершенно непонятно. Фраза "Параметр offset является строкой, выделенной скобками в шаблоне URL" ясности не вносит, ибо в примере 6 пар скобок, и принцип выбора скобок опять же непонятен.

Здесь задаётся *правило выборки значения* offset, на основе шаблона (он в виде регулярного выражения, читайте /ch03.html).

Вот шаблон (паттерн):
r'^time/plus/(\d{1,2})/$'
Вот суббаттерн в нём:
(\d{1,2})
Об этих круглых скобках и шла речь.

Фигурные скобки {} указывают количество повторов выражения, за которым идут, здесь это \d (цифра). Т. е. цифра может быть повторена от 1 до 2 раз. Потому максимально возможное значение - 99.

$_ - это вы из Perl взяли? В данном случае такой уровень абстракции, что нет смысла вникать, чем именно обрабатывается шаблон и что именно передаёт полученные значения из HttpRequest в представление. Этим занимается Django за нас. Видимо, библиотеки patterns и url.

djoa
djoa 2 месяца, 1 неделя прошло
Ответ | Ссылка

Субпаттернов в одном выражении может быть несколько, все совпадения по ним будут отданы в виде параметров в функцию-обработчик представления.
Вот пример с двумя субпаттернами:
from django.conf.urls import patterns, include, url
from first.views import my_test

urlpatterns = patterns('',
(r'^test/(\d{3,5})/(\d)/$', my_test),
)

А в views.py функция будет выглядеть так:
# -*- coding: utf-8 -*-
from django.http import Http404, HttpResponse

def my_test(request, many, one):
try:
many = int(many)
one = int(one)
except ValueError:
raise Http404()
html = '<html><body>I have %s joyes and only %s troubles!</body></html>' % (many, one)
return HttpResponse(html)

Пример URL'а: http://127.0.0.1:8000/test/111/2/

KonstantinSeleznev
KonstantinSeleznev 1 месяц прошло
Ответ | Ссылка

Респект тебе, djoa, сразу внёс ясность в этом моменте

Michelle--
Michelle-- 2 недели, 5 дней прошло
Ответ | Ссылка

Спасибо за перевод!
ps. У кого возникают вопросы настоятельно рекомендую ознакомится с regular expressions - регулярными вырежениями, линк на википедию. Регулярки в любом кодинге полезны, желательно, чтобы вы имели представление о них. http://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F