Главное руководство по редиректам Django

Главное руководство по редиректам Django

Когда вы создаете веб-приложение Python с помощью Django framework, вам в какой-то момент придется перенаправить пользователя с одного URL-адреса на другой.

В этом руководстве вы узнаете все, что вам нужно знать о перенаправлениях HTTP и о том, как с ними обращаться, в Django. В конце этого урока вы будете:

  • Уметь перенаправлять пользователя с одного URL на другой URL

  • Знайте разницу между временным и постоянным перенаправлениями

  • Избегайте распространенных ошибок при работе с перенаправлениями

В этом руководстве предполагается, что вы знакомы с основными строительными блоками приложения Django, такими как views и https://docs.djangoproject. .com/ru/2.1/themes/http/urls/[шаблоны URL].

Джанго перенаправления: супер простой пример

В Django вы перенаправляете пользователя на другой URL, возвращая экземпляр + HttpResponseRedirect + или + HttpResponsePermanentRedirect + из своего представления. Самый простой способ сделать это - использовать функцию https://docs.djangoproject.com/en/2.1/topics/http/shortcuts/#redirect [+ redirect () +] из модуля `+ django.shortcuts + `. Вот пример:

# views.py
from django.shortcuts import redirect

def redirect_view(request):
    response = redirect('/redirect-success/')
    return response

Просто вызовите + redirect () + с URL в вашем представлении. Он вернет класс + HttpResponseRedirect +, который вы затем вернете из своего представления.

Представление, возвращающее перенаправление, должно быть добавлено к вашему + urls.py +, как и любое другое представление:

# urls.py
from django.urls import path

from .views import redirect_view

urlpatterns = [
    path('/redirect/', redirect_view)
    # ... more URL patterns here
]

Предполагая, что это основной + urls.py + вашего проекта Django, URL /redirect/ теперь перенаправляет на /redirect-success/.

Чтобы избежать жесткого кодирования URL, вы можете вызвать + redirect () + с именем представления или шаблона URL или модели, чтобы избежать жесткого кодирования URL перенаправления. Вы также можете создать постоянный редирект, передав ключевой аргумент + constant = True +.

Эта статья могла бы закончиться здесь, но тогда ее вряд ли можно было бы назвать «Окончательное руководство по перенаправлениям Django». Мы подробнее рассмотрим функцию + redirect () + через минуту, а также подробно рассмотрим подробности кодов состояния HTTP и различных классов + HttpRedirectResponse +, но давайте сделаем шаг назад и начнем с фундаментальный вопрос.

Зачем перенаправлять

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

  • Если вы не вошли в систему и не запросили URL-адрес, требующий аутентификации, например, администратор Django, Django перенаправит вас на страницу входа.

  • При успешном входе в систему Django перенаправляет вас на URL, который вы запрашивали изначально.

  • Когда вы изменяете свой пароль с помощью администратора Django, вы перенаправляетесь на страницу, которая указывает, что изменение прошло успешно.

  • Когда вы создаете объект в админке Django, Django перенаправляет вас в список объектов.

Как будет выглядеть альтернативная реализация без перенаправлений? Если пользователь должен войти в систему, чтобы просмотреть страницу, вы можете просто отобразить страницу, которая говорит что-то вроде «Нажмите здесь, чтобы войти». Это будет работать, но это будет неудобно для пользователя.

Сокращения URL-адресов, такие как http://bit.ly, являются еще одним примером того, где перенаправления пригодятся: вы вводите короткий URL-адрес в адресную строку браузера и затем перенаправляетесь на страницу с длинным, громоздким URL-адресом.

В других случаях перенаправления не просто удобны. Перенаправления являются важным инструментом для руководства пользователя через веб-приложение. После выполнения какой-либо операции с побочными эффектами, например создания или удаления объекта, рекомендуется перенаправить на другой URL-адрес, чтобы предотвратить случайное выполнение операции дважды.

Одним из примеров такого использования перенаправлений является обработка формы, когда пользователь перенаправляется на другой URL-адрес после успешной отправки формы. Вот пример кода, который иллюстрирует, как вы обычно обрабатываете форму:

 1 from django import forms
 2 from django.http import HttpResponseRedirect
 3 from django.shortcuts import redirect, render
 4
 5 def send_message(name, message):
 6     # Code for actually sending the message goes here
 7
 8 class ContactForm(forms.Form):
 9     name = forms.CharField()
10     message = forms.CharField(widget=forms.Textarea)
11
12 def contact_view(request):
13     # The request method 'POST' indicates
14     # that the form was submitted
15     if request.method == 'POST':  # 1
16         # Create a form instance with the submitted data
17         form = ContactForm(request.POST)  # 2
18         # Validate the form
19         if form.is_valid():  # 3
20             # If the form is valid, perform some kind of
21             # operation, for example sending a message
22             send_message(
23                 form.cleaned_data['name'],
24                 form.cleaned_data['message']
25             )
26             # After the operation was successful,
27             # redirect to some other page
28             return redirect('/success/')  # 4
29     else:  # 5
30         # Create an empty form instance
31         form = ContactForm()
32
33     return render(request, 'contact_form.html', {'form': form})

Цель этого представления - отобразить и обработать контактную форму, которая позволяет пользователю отправить сообщение. Давайте следовать этому шаг за шагом:

  1. Сначала представление смотрит на метод запроса. Когда пользователь посещает URL, связанный с этим представлением, браузер выполняет запрос + GET +.

  2. Если представление вызывается с помощью запроса + POST +, данные + POST + используются для создания экземпляра объекта + ContactForm +.

  3. Если форма верна, данные формы передаются в + send_message () +. Эта функция не относится к этому контексту и поэтому здесь не показана.

  4. После отправки сообщения представление возвращает перенаправление на URL /success/. Это тот шаг, который нас интересует. Для простоты URL здесь жестко запрограммирован. Позже вы увидите, как этого избежать.

  5. Если представление получает запрос + GET + (или, если быть точным, любой вид запроса, который не является запросом + POST +), оно создает экземпляр + ContactForm + и использует + django.shortcuts.render. () + `для отображения шаблона + contact_form.html + `.

Если пользователь теперь нажимает reload, перезагружается только URL /success/. Без перенаправления перезагрузка страницы повторно отправит форму и отправит другое сообщение.

За кулисами: как работает перенаправление HTTP

Теперь вы знаете, почему перенаправления имеют смысл, но как они работают? Давайте кратко рассмотрим, что происходит, когда вы вводите URL-адрес в адресную строку вашего веб-браузера.

Быстрый учебник по HTTP

Предположим, вы создали приложение Django с представлением «Hello World», которое обрабатывает путь «/hello/». Вы запускаете свое приложение на сервере разработки Django, поэтому полный URL-адрес: + http://127.0.0.1: 8000/hello/+.

Когда вы вводите этот URL в браузер, он подключается к порту + 8000 + на сервере с IP-адресом + 127.0.0.1 + и отправляет HTTP-запрос + GET + для пути `/hello/ `. Сервер отвечает HTTP-ответом.

HTTP основывается на тексте, поэтому относительно легко переключаться между клиентом и сервером. Вы можете использовать инструмент командной строки https://curl.haxx.se/docs/manpage.html [+ curl +] с параметром + - include +, чтобы просмотреть полный HTTP-ответ, включая заголовки, как это:

$ curl --include http://127.0.0.1:8000/hello/
HTTP/1.1 200 OK
Date: Sun, 01 Jul 2018 20:32:55 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 11

Hello World

Как видите, HTTP-ответ начинается со строки состояния, которая содержит код состояния и сообщение о состоянии. За строкой состояния следует произвольное количество заголовков HTTP. Пустая строка указывает конец заголовков и начало тела ответа, которое содержит фактические данные, которые сервер хочет отправить.

HTTP перенаправляет коды состояния

Как выглядит ответ перенаправления? Предположим, что путь /redirect/ обрабатывается + redirect_view () +, показанным ранее. Если вы обращаетесь к + http://127.0.0.1: 8000/redirect/+ с помощью + curl +, ваша консоль выглядит следующим образом:

$ curl --include http://127.0.0.1:8000/redirect/
HTTP/1.1 302 Found
Date: Sun, 01 Jul 2018 20:35:34 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Content-Type: text/html; charset=utf-8
Location:/redirect-success/
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Эти два ответа могут выглядеть одинаково, но есть некоторые ключевые различия. Редирект:

  • Возвращает другой код состояния (+ 302 + против + 200 +)

  • Содержит заголовок + Location + с относительным URL

  • Заканчивается пустой строкой, потому что тело ответа перенаправления пусто

Основным отличием является код статуса. Спецификация стандарта HTTP гласит следующее:

_ Код состояния 302 (Найдено) указывает, что целевой ресурс временно находится под другим URI. Поскольку перенаправление может иногда изменяться, клиент должен продолжать использовать эффективный URI запроса для будущих запросов. Серверу СЛЕДУЕТ генерировать поле заголовка Location в ответе, содержащее ссылку на URI для другого URI. Пользовательский агент МОЖЕТ использовать значение поля Location для автоматического перенаправления. (Https://tools.ietf.org/html/rfc7231#section-6.4[Source]) _

Другими словами, всякий раз, когда сервер отправляет код состояния «+ 302 +», он говорит клиенту: «Эй, сейчас то, что вы ищете, можно найти в этом другом месте».

Ключевая фраза в спецификации: «МОЖЕТ использовать значение поля Location для автоматического перенаправления». Это означает, что вы не можете заставить клиента загрузить другой URL. Клиент может выбрать ожидание подтверждения пользователя или отказаться от загрузки URL-адреса вообще.

Теперь вы знаете, что перенаправление - это просто HTTP-ответ с кодом состояния «+ 3xx » и заголовком « Location ». Ключевым моментом здесь является то, что перенаправление HTTP похоже на любой старый ответ HTTP, но с пустым телом, кодом статуса 3xx и заголовком ` Location +`.

Это оно. Мы ненадолго свяжем это с Django, но сначала давайте взглянем на два типа перенаправлений в этом диапазоне кодов состояния «+ 3xx +» и посмотрим, почему они имеют значение, когда дело доходит до веб-разработки.

Временные против Постоянные перенаправления

Стандарт HTTP определяет несколько кодов состояния перенаправления, все в диапазоне «+ 3xx +». Двумя наиболее распространенными кодами состояния являются «+301 Permanent Redirect +» и «+302 Found +».

Код состояния +302 Found + указывает на временное перенаправление. Временный редирект говорит: «В данный момент то, что вы ищете, можно найти по этому другому адресу». Думайте об этом как о вывеске магазина: «Наш магазин в настоящее время закрыт на ремонт. Пожалуйста, зайдите в наш другой магазин за углом. Поскольку это только временно, вы будете проверять оригинальный адрес при следующем посещении магазина.

*Примечание:* В HTTP 1.0 сообщение с кодом состояния 302 было `+ Temporary Redirect +`. Сообщение было изменено на `+ Found +` в HTTP 1.1.

Как следует из названия, постоянные перенаправления должны быть постоянными. Постоянное перенаправление говорит браузеру: «То, что вы ищете, больше не находится по этому адресу. Теперь он находится по этому новому адресу и никогда больше не будет по старому адресу ».

Постоянное перенаправление похоже на вывеску магазина: «Мы переехали. Наш новый магазин не за горами ». Это изменение является постоянным, поэтому в следующий раз, когда вы захотите пойти в магазин, вы сразу перейдете на новый адрес.

*Примечание:* Постоянные перенаправления могут иметь непредвиденные последствия. Завершите это руководство, прежде чем использовать постоянное перенаправление, или сразу перейдите к разделу «Постоянные перенаправления».

Браузеры ведут себя аналогично при обработке перенаправлений: когда URL-адрес возвращает постоянный ответ на перенаправление, этот ответ кэшируется. В следующий раз, когда браузер обнаруживает старый URL-адрес, он запоминает перенаправление и напрямую запрашивает новый адрес.

Кэширование перенаправления сохраняет ненужный запрос и обеспечивает лучший и быстрый пользовательский опыт.

Кроме того, различие между временными и постоянными перенаправлениями имеет значение для поисковой оптимизации.

Перенаправления в Джанго

Теперь вы знаете, что перенаправление - это просто HTTP-ответ с кодом состояния «+ 3xx » и заголовком « Location +».

Вы можете создать такой ответ самостоятельно из обычного объекта + HttpResponse +:

def hand_crafted_redirect_view(request):
  response = HttpResponse(status=302)
  response['Location'] = '/redirect/success/'
  return response

Это техническое решение является технически правильным, но оно требует много печатания.

Класс + HTTPResponseRedirect +

Вы можете сэкономить некоторую печать с помощью класса + HttpResponseRedirect +, подкласса + HttpResponse +. Просто создайте экземпляр класса с URL, который вы хотите перенаправить в качестве первого аргумента, и класс установит правильный статус и заголовок Location:

def redirect_view(request):
  return HttpResponseRedirect('/redirect/success/')

Вы можете поиграть с классом + HttpResponseRedirect + в оболочке Python, чтобы увидеть, что вы получаете:

>>>

>>> from django.http import HttpResponseRedirect
>>> redirect = HttpResponseRedirect('/redirect/success/')
>>> redirect.status_code
302
>>> redirect['Location']
'/redirect/success/'

Существует также класс для постоянных перенаправлений, который удачно называется + HttpResponsePermanentRedirect +. Он работает так же, как + HttpResponseRedirect +, с той лишь разницей, что он имеет код состояния +301 (перемещено постоянно) +.

*Примечание:* В приведенных выше примерах URL-адреса перенаправления жестко запрограммированы. Жесткое кодирование URL-адресов - это плохая практика: если URL-адрес когда-либо изменяется, вам необходимо выполнить поиск по всему коду и изменить все вхождения. Давайте исправим это!

Вы можете использовать https://docs.djangoproject.com/en/2.1/ref/urlresolvers/#reverse [+ django.urls.reverse () +] для создания URL, но для вас есть более удобный способ увидим в следующем разделе.

Функция + redirect () +

Чтобы сделать вашу жизнь проще, Django предоставляет универсальную функцию ярлыков, которую вы уже видели во введении: `+django.shortcuts. перенаправление () + `.

Вы можете вызвать эту функцию с помощью:

Будут предприняты соответствующие шаги, чтобы превратить аргументы в URL и вернуть + HTTPResponseRedirect +. Если вы передадите + constant = True +, он вернет экземпляр + HttpResponsePermanentRedirect +, что приведет к перманентному перенаправлению.

Вот три примера, иллюстрирующие различные варианты использования:

  1. Передача модели:

from django.shortcuts import redirect

def model_redirect_view(request):
    product = Product.objects.filter(featured=True).first()
    return redirect(product)

+ + redirect () + вызовет + product.get_absolute_url () + и будет использовать результат в качестве цели перенаправления. Если данный класс, в данном случае + Product +, не имеет метода + get_absolute_url () +, это не удастся с помощью + TypeError +. . Передача URL-адреса и аргументов:

from django.shortcuts import redirect

def fixed_featured_product_view(request):
    ...
    product_id = settings.FEATURED_PRODUCT_ID
    return redirect('product_detail', product_id=product_id)

+ + redirect () + будет пытаться использовать заданные аргументы для обращения URL. В этом примере предполагается, что шаблоны URL содержат шаблон, подобный следующему:

path('/product/<product_id>/', 'product_detail_view', name='product_detail')
  1. Передача URL:

from django.shortcuts import redirect

def featured_product_view(request):
    return redirect('/products/42/')

+ + redirect () + будет обрабатывать любую строку, содержащую / или +. +, как URL и использовать ее как цель перенаправления.

+ RedirectView + Представление на основе классов

Если у вас есть представление, которое ничего не делает, но возвращает перенаправление, вы можете использовать представление на основе классов `+ django.views.generic.base.RedirectView + `.

Вы можете адаптировать + RedirectView + к вашим потребностям с помощью различных атрибутов.

Если у класса есть атрибут + .url +, он будет использоваться как URL перенаправления. Заполнители форматирования строки заменяются именованными аргументами из URL:

# urls.py
from django.urls import path
from .views import SearchRedirectView

urlpatterns = [
    path('/search/<term>/', SearchRedirectView.as_view())
]

# views.py
from django.views.generic.base import RedirectView

class SearchRedirectView(RedirectView):
  url = 'https://google.com/?q=%(term)s'

Шаблон URL определяет аргумент + term +, который используется в + SearchRedirectView + для создания URL перенаправления. Путь /search/kittens/ в вашем приложении перенаправит вас на + https://google.com/? Q = kittens +.

Вместо подкласса + RedirectView + для перезаписи атрибута + url + вы также можете передать ключевой аргумент + url + в + as_view () + в вашем + urlpatterns +:

#urls.py
from django.views.generic.base import RedirectView

urlpatterns = [
    path('/search/<term>/',
         RedirectView.as_view(url='https://google.com/?q=%(term)s')),
]

Вы также можете перезаписать + get_redirect_url () +, чтобы получить полностью настраиваемое поведение:

from random import choice
from django.views.generic.base import RedirectView

class RandomAnimalView(RedirectView):

     animal_urls = ['/dog/', '/cat/', '/parrot/']
     is_permanent = True

     def get_redirect_url(*args, **kwargs):
        return choice(self.animal_urls)

Это представление на основе классов перенаправляет на URL, выбранный случайным образом из + .animal_urls +.

+ django.views.generic.base.RedirectView + предлагает еще несколько хуков для настройки. Вот полный список:

  • + + .Url + Если этот атрибут установлен, это должна быть строка с URL-адресом для перенаправления. Если он содержит заполнители форматирования строк, такие как +% (name) s +, они раскрываются с помощью аргументов ключевого слова, передаваемых представлению.

  • + + .Pattern_name + Если этот атрибут установлен, это должно быть имя шаблона URL для перенаправления. Любые позиционные и ключевые аргументы, передаваемые представлению, используются для изменения шаблона URL.

  • + + .Permanent + Если этот атрибут + True +, представление возвращает постоянное перенаправление. По умолчанию это + False +.

  • + + .Query_string + Если этот атрибут + True +, представление добавляет любую предоставленную строку запроса к URL перенаправления. Если это + False + (по умолчанию), строка запроса отбрасывается.

  • + get_redirect_url ( args, * kwargs) + + Этот метод отвечает за создание URL перенаправления. Если этот метод возвращает + None +, представление возвращает статус +410 Gone +. + Реализация по умолчанию сначала проверяет + .url +. Он обрабатывает + .url + как строку «https://realpython.com/python-string-formatting/[format» в «старом стиле», используя любые параметры именованных URL, передаваемые представлению, для расширения любых именованных спецификаторов формата. + Если + .url + не установлен, он проверяет, установлен ли + .pattern_name +. Если это так, он использует его для обратного URL-адреса с любыми позиционными и ключевыми аргументами, которые он получил. + Вы можете изменить это поведение любым способом, переписав этот метод. Просто убедитесь, что он возвращает строку, содержащую URL.

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

Отличным инструментом для понимания класса представлений на основе классов является веб-сайт Classy Представления на основе классов.

Вы можете реализовать функциональность + RandomAnimalView + из приведенного выше примера с помощью этого простого функционального представления:

from random import choice
from django.shortcuts import redirect

def random_animal_view(request):
    animal_urls = ['/dog/', '/cat/', '/parrot/']
    return redirect(choice(animal_urls))

Как видите, подход на основе классов не дает очевидных преимуществ при добавлении некоторой скрытой сложности. Возникает вопрос: когда вы должны использовать + RedirectView +?

Если вы хотите добавить перенаправление непосредственно в ваш файл + urls.py +, использование + RedirectView + имеет смысл. Но если вы обнаружите, что перезаписываете + get_redirect_url +, функциональное представление может быть проще для понимания и более гибким для будущих улучшений.

Расширенное использование

Если вы знаете, что, возможно, захотите использовать + django.shortcuts.redirect () +, перенаправление на другой URL довольно просто. Но есть несколько продвинутых вариантов использования, которые не так очевидны.

Передача параметров с перенаправлениями

Иногда вы хотите передать некоторые параметры представлению, на которое вы перенаправляете. Лучше всего передать данные в строке запроса URL-адреса перенаправления, что означает перенаправление на URL-адрес, например так:

http://example.com/redirect-path/?parameter=value

Предположим, вы хотите перенаправить из + some_view () + в + product_view () +, но передаете необязательный параметр + category +:

from django.urls import reverse
from urllib.parse import urlencode

def some_view(request):
    ...
    base_url = reverse('product_view')  # 1/products/
    query_string =  urlencode({'category': category.id})  # 2 category=42
    url = '{}?{}'.format(base_url, query_string)  # 3/products/?category=42
    return redirect(url)  # 4

def product_view(request):
    category_id = request.GET.get('category')  # 5
    # Do something with category_id

Код в этом примере довольно плотный, поэтому давайте следуем ему шаг за шагом:

  1. Во-первых, вы используете + django.urls.reverse () +, чтобы получить сопоставление URL с + product_view () +.

  2. Далее вы должны построить строку запроса. Это часть после знака вопроса. Для этого рекомендуется использовать + urllib.urlparse.urlencode () +, поскольку он позаботится о правильном кодировании любых специальных символов.

  3. Теперь вы должны объединить + base_url + и + query_string + со знаком вопроса. Форматная строка отлично подходит для этого.

  4. Наконец, вы передаете + url + в + django.shortcuts.redirect () + или в класс ответа перенаправления.

  5. В + product_view () +, вашей цели перенаправления, параметр будет доступен в словаре + request.GET +. Параметр может отсутствовать, поэтому вы должны использовать + запросы.GET.get ('категория') + вместо + запросы.GET ['категория'] +. Первый возвращает + None +, когда параметр не существует, тогда как второй вызовет исключение.

    *Примечание:* Обязательно проверяйте любые данные, которые вы читаете из строк запроса. Может показаться, что эти данные находятся под вашим контролем, потому что вы создали URL перенаправления.

В действительности, перенаправление может управляться пользователем и не должно быть доверенным, как любой другой пользовательский ввод. Без надлежащей проверки an[an может получить несанкционированный доступ.

Специальные коды перенаправления

Django предоставляет классы ответов HTTP для кодов состояния + 301 + и + 302 +. Они должны охватывать большинство случаев использования, но если вам когда-либо придется возвращать коды состояния + 303 +, + 307 + или + 308 +, вы можете довольно легко создать свой собственный класс ответа. Просто создайте подкласс + HttpResponseRedirectBase + и перезапишите атрибут + status_code +:

class HttpResponseTemporaryRedirect(HttpResponseRedirectBase):
    status_code = 307

Кроме того, вы можете использовать метод + django.shortcuts.redirect () + для создания объекта ответа и изменения возвращаемого значения. Этот подход имеет смысл, когда у вас есть имя представления или URL или модель, на которую вы хотите перенаправить:

def temporary_redirect_view(request):
    response = redirect('success_view')
    response.status_code = 307
    return response
*Примечание:* На самом деле существует третий класс с кодом состояния в диапазоне `+ 3xx +`: `+ HttpResponseNotModified +`, с кодом состояния `+ 304 +`. Это указывает на то, что URL-адрес содержимого не изменился и что клиент может использовать кэшированную версию.

Можно утверждать, что ответ «+304 Not Modified +» перенаправляет на кэшированную версию URL, но это немного натянуто. Следовательно, он больше не указан в разделе Redirection 3xx ” стандарта HTTP.

Ловушки

Перенаправления, которые просто не будут перенаправлять

Простота + django.shortcuts.redirect () + может быть обманчива. Сама функция не выполняет перенаправление: она просто возвращает объект ответа перенаправления. Вы должны вернуть этот объект ответа из своего представления (или в промежуточном программном обеспечении). В противном случае перенаправления не произойдет.

Но даже если вы знаете, что простого вызова + redirect () + недостаточно, эту ошибку легко внедрить в работающее приложение с помощью простого рефакторинга. Вот пример, чтобы проиллюстрировать это.

Предположим, вы строите магазин и у вас есть представление, которое отвечает за показ продукта. Если продукт не существует, вы перенаправляете на домашнюю страницу:

def product_view(request, product_id):
    try:
        product = Product.objects.get(pk=product_id)
    except Product.DoesNotExist:
        return redirect('/')
    return render(request, 'product_detail.html', {'product': product})

Теперь вы хотите добавить второе представление для отображения отзывов покупателей о товаре. Он также должен перенаправить на домашнюю страницу для несуществующих продуктов, поэтому в качестве первого шага вы извлекаете эту функциональность из + product_view () + в вспомогательную функцию + get_product_or_redirect () +:

def get_product_or_redirect(product_id):
    try:
        return Product.objects.get(pk=product_id)
    except Product.DoesNotExist:
        return redirect('/')

def product_view(request, product_id):
    product = get_product_or_redirect(product_id)
    return render(request, 'product_detail.html', {'product': product})

К сожалению, после рефакторинга редирект больше не работает.

Перенаправления, которые просто не перестанут перенаправлять

При работе с перенаправлениями вы можете случайно создать цикл перенаправления, если URL-адрес A возвращает перенаправление, указывающее на URL-адрес B, который возвращает перенаправление на URL-адрес A, и так далее. Большинство клиентов HTTP обнаруживают этот тип цикла перенаправления и после нескольких запросов отображают сообщение об ошибке.

К сожалению, этот тип ошибки может быть сложно обнаружить, потому что все хорошо на стороне сервера. Если ваши пользователи не жалуются на проблему, единственным признаком того, что что-то может быть не так, является то, что вы получили несколько запросов от одного клиента, все из которых приводят к ответу на перенаправление в быстрой последовательности, но нет ответа с +200 OK + ` положение дел.

Вот простой пример цикла перенаправления:

def a_view(request):
    return redirect('another_view')

def another_view(request):
    return redirect('a_view')

Этот пример иллюстрирует принцип, но он слишком упрощен. Циклы перенаправления, с которыми вы столкнетесь в реальной жизни, вероятно, будет труднее обнаружить. Давайте посмотрим на более сложный пример:

def featured_products_view(request):
    featured_products = Product.objects.filter(featured=True)
    if len(featured_products == 1):
        return redirect('product_view', kwargs={'product_id': featured_products[0].id})
    return render(request, 'featured_products.html', {'product': featured_products})

def product_view(request, product_id):
    try:
        product = Product.objects.get(pk=product_id, in_stock=True)
    except Product.DoesNotExist:
        return redirect('featured_products_view')
    return render(request, 'product_detail.html', {'product': product})

+ featured_products_view () + извлекает все рекомендуемые продукты, другими словами, экземпляры + Product + с + .featured +, установленным в + True +. Если существует только один рекомендуемый продукт, он перенаправляется непосредственно в + product_view () +. В противном случае он отображает шаблон с набором запросов + featured_products +.

+ Product_view + выглядит знакомо из предыдущего раздела, но имеет два небольших отличия:

  • Представление пытается извлечь + Product +, который есть в наличии, на что указывает + .in_stock +, установленный в + True +.

  • Представление перенаправляется на + featured_products_view () +, если товара нет на складе.

Эта логика прекрасно работает, пока ваш магазин не станет жертвой собственного успеха и пока один из представленных вами товаров не поступит в продажу. Если вы установили + .in_stock + в + False +, но забыли установить для + .featured + также + False +, то любой посетитель вашего + feature_product_view () + теперь будет зависать в цикле перенаправления ,

Не существует пуленепробиваемого способа предотвращения такого рода ошибок, но хорошей отправной точкой является проверка того, использует ли перенаправляемое представление само перенаправление.

Постоянные перенаправления постоянны

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

Когда браузер получает постоянный ответ о перенаправлении для URL, он кэширует этот ответ на неопределенный срок. Каждый раз, когда вы запрашиваете старый URL в будущем, браузер не загружает его и напрямую загружает новый URL.

Может быть довольно сложно убедить браузер загрузить URL, который когда-то возвращал постоянное перенаправление. Google Chrome особенно агрессивен, когда речь идет о кэшировании перенаправлений.

Почему это может быть проблемой?

Представьте, что вы хотите создать веб-приложение с Django. Вы регистрируете свой домен на + myawesomedjangowebapp.com +. В качестве первого шага вы устанавливаете приложение для блога по адресу + https://myawesomedjangowebapp.com/blog/+, чтобы создать список рассылки для запуска.

Домашняя страница вашего сайта по адресу + https://myawesomedjangowebapp.com/+ все еще находится в стадии разработки, поэтому вы перенаправляете на + https://myawesomedjangowebapp.com/blog/+. Вы решили использовать постоянное перенаправление, потому что слышали, что постоянные перенаправления кэшируются, а кэширование ускоряет и ускоряет процесс, потому что скорость является фактором ранжирования в результатах поиска Google.

Оказывается, вы не только великий разработчик, но и талантливый писатель. Ваш блог становится популярным, и ваш список рассылки запуска растет. Через пару месяцев ваше приложение готово. Теперь у него есть блестящая домашняя страница, и вы наконец удалите перенаправление.

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

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

Что случилось? Читатели вашего блога посетили + https://myawesomedjangowebapp.com/+, когда перенаправление на + https://myawesomedjangowebapp.com/blog/+ было еще активным. Поскольку это был постоянный редирект, он кэшировался в их браузерах.

Когда они нажали на ссылку в вашем объявлении о запуске, их браузеры никогда не удосужились проверить вашу новую домашнюю страницу и сразу перешли к вашему блогу. Вместо того, чтобы праздновать ваш успешный запуск, вы заняты инструктированием своих пользователей, как поиграть с + chrome://net-internals +, чтобы сбросить кеш своих браузеров.

Постоянный характер перенаправлений также может укусить вас во время разработки на локальном компьютере. Давайте вернемся к тому моменту, когда вы реализовали этот роковой постоянный редирект для myawesomedjangowebapp.com.

Вы запускаете сервер разработки и открываете + http://127.0.0.1: 8000/+. Как и предполагалось, ваше приложение перенаправляет ваш браузер на + http://127.0.0.1: 8000/blog/+. Довольный своей работой, вы останавливаете сервер разработки и идете на обед.

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

Но подождите, что здесь происходит? Домашняя страница не работает, теперь она возвращает 404! Из-за спада во второй половине дня вам потребуется некоторое время, чтобы заметить, что вы перенаправлены на + http://127.0.0.1: 8000/blog/+, которого нет в проекте клиента.

Для браузера не имеет значения, что URL + http://127.0.0.1: 8000/+ теперь обслуживает совершенно другое приложение. Все, что имеет значение для браузера, это то, что этот URL когда-то возвращал постоянное перенаправление на + http://127.0.0.1: 8000/blog/+.

Вывод из этой истории заключается в том, что вы должны использовать постоянные перенаправления только на те URL-адреса, которые вы никогда не собираетесь использовать снова. Есть место для перенаправлений, но вы должны знать об их последствиях.

Даже если вы уверены, что действительно нуждаетесь в постоянном перенаправлении, рекомендуется сначала выполнить временное перенаправление и переключиться на его постоянного двоюродного брата, только если вы на 100% уверены, что все работает, как задумано.

Непроверенные перенаправления могут поставить под угрозу безопасность

С точки зрения безопасности, перенаправления являются относительно безопасным методом. Злоумышленник не может взломать сайт с помощью перенаправления. В конце концов, перенаправление просто перенаправляет на URL-адрес, который злоумышленник может просто ввести в адресной строке своего браузера.

Однако если вы используете какой-либо пользовательский ввод, например параметр URL, без надлежащей проверки в качестве URL перенаправления, злоумышленник может использовать его для фишинг-атаки. Этот вид перенаправления называется open или непроверенным перенаправлением.

Существуют законные варианты использования для перенаправления на URL, который читается из пользовательского ввода. Ярким примером является вид входа в систему Django. Он принимает параметр URL + next +, который содержит URL страницы, на которую перенаправляется пользователь после входа в систему. Чтобы перенаправить пользователя в его профиль после входа в систему, URL-адрес может выглядеть следующим образом:

https://myawesomedjangowebapp.com/login/?next=/profile/

Django действительно проверяет параметр + next +, но давайте на секунду предположим, что он этого не делает.

Без проверки злоумышленник может создать URL-адрес, который перенаправляет пользователя на сайт, находящийся под его контролем, например:

https://myawesomedjangowebapp.com/login/?next=https://myawesomedjangowebapp.co/profile/

Веб-сайт + myawesomedjangowebapp.co + может затем отобразить сообщение об ошибке и заставить пользователя снова ввести свои учетные данные.

Лучший способ избежать открытых перенаправлений - не использовать ввод пользователя при создании URL-адреса перенаправления.

Если вы не можете быть уверены, что URL-адрес безопасен для перенаправления, вы можете использовать функцию + django.utils.http.is_safe_url () + для его проверки. Строка документации объясняет его использование довольно хорошо:

_ _ + is_safe_url (url, host = None, allow_hosts = None, require_https = False) +

Вернуть + True +, если URL-адрес является безопасным перенаправлением (т.е. он не указывает на другой хост и использует безопасную схему). Всегда возвращайте + False + в пустом URL. Если + require_https + равно + True +, только «https» будет считаться допустимой схемой, в отличие от «http» и «https» со значением по умолчанию «+ False +». (Https://github.com/django/django/blob/53a3d2b2454ff9a612a376f58bb7c61733f82d12/django/utils/http.py#L280[Source]) _ _

Давайте посмотрим на некоторые примеры.

Относительный URL считается безопасным:

>>>

>>> # Import the function first.
>>> from django.utils.http import is_safe_url
>>> is_safe_url('/profile/')
True

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

>>>

>>> is_safe_url('https://myawesomedjangowebapp.com/profile/')
False

URL-адрес, указывающий на другой хост, считается безопасным, если его хост указан в + allow_hosts +:

>>>

>>> is_safe_url('https://myawesomedjangowebapp.com/profile/',
...             allowed_hosts={'myawesomedjangowebapp.com'})
True

Если аргумент + require_https + равен + True +, URL-адрес, использующий схему + http +, не считается безопасным:

>>>

>>> is_safe_url('http://myawesomedjangowebapp.com/profile/',
...             allowed_hosts={'myawesomedjangowebapp.com'},
...             require_https=True)
False

Резюме

Это завершает это руководство по HTTP перенаправлениям с Django. Поздравляем: теперь вы затронули все аспекты перенаправлений, начиная с низкоуровневых деталей протокола HTTP и заканчивая высокоуровневым способом работы с ними в Django.

Вы узнали, как выглядит перенаправление HTTP, каковы различные коды состояния, и как отличаются постоянные и временные перенаправления. Эти знания не являются специфическими для Django и полезны для веб-разработки на любом языке.

Теперь вы можете выполнить перенаправление с помощью Django, либо используя классы ответов перенаправления + HttpResponseRedirect + и + HttpResponsePermanentRedirect +, либо с помощью вспомогательной функции + django.shortcuts.redirect () +. Вы видели решения для нескольких продвинутых вариантов использования и знаете, как избежать распространенных ошибок.

Если у вас есть какие-либо дополнительные вопросы о HTTP-перенаправлениях, оставьте комментарий ниже, а пока, рад перенаправлению!

Related