Error 301 django

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

Когда вы создаете веб-приложение 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-адрес когда-либо изменяется, вам необходимо выполнить поиск по всему коду и изменить все вхождения. Давайте исправим это!

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

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

  • Экземпляр модели или любой другой объект с помощью метода https://docs.djangoproject.com/en/2.1/ref/models/instances/#get-absolute-url [+ get_absolute_url () +] метод

  • URL или имя представления и позиционные и/или ключевые аргументы

  • URL

Будут предприняты соответствующие шаги, чтобы превратить аргументы в 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.

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

Вы можете реализовать функциональность + 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.

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

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

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

Когда они нажали на ссылку в вашем объявлении о запуске, их браузеры никогда не удосужились проверить вашу новую домашнюю страницу и сразу перешли к вашему блогу. Вместо того, чтобы праздновать ваш успешный запуск, вы заняты инструктированием своих пользователей, как поиграть с + 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-перенаправлениях, оставьте комментарий ниже, а пока, рад перенаправлению!

Рекомендации

  • Django документация: + + django.http.HttpResponseRedirect +

  • Django документация: + django.shortcuts.render () +

  • Django документация: + django.views.generic.base.RedirectView +

  • RFC 7231: Протокол передачи гипертекста (HTTP/1.1): семантика и контент — 6.4 Перенаправление 3xx

  • CWE-601: перенаправление URL-адресов на ненадежный сайт («Открыть перенаправление»)

Архив проекта: lesson-3-coolsite.zip

Продолжаем тему
маршрутизации. И для начала уберем фрагмент ‘women/’ из пути URL’ов для
приложения women:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('women.urls')),
]

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

127.0.0.1:8000/cats/1/

127.0.0.1:8000/cats/2/

127.0.0.1:8000/cats/3/

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

urlpatterns = [
    path('', index),
    path('cats/<int:catid>/', categories),
]

Смотрите, здесь
в угловых скобках записан параметр catid, который имеет
тип int – целочисленный.
Такой путь будет соответствовать любым комбинациям URL с фрагментом ‘cats/число/’. И,
далее, в функции представления categories мы уже можем использовать этот
параметр:

def categories(request, catid):
    return HttpResponse(f"<h1>Статьи по категориям</h1>{catid}</p>")

Как видите, все
предельно просто. Помимо типа int в Django можно
использовать и такие типы:

  • str – любая не
    пустая строка, исключая символ ‘/’;

  • int – любое
    положительное целое число, включая 0;

  • slug – слаг, то
    есть, латиница ASCII таблицы, символы дефиса и
    подчеркивания;

  • uuid – цифры, малые
    латинские символы ASCII, дефис;

  • path – любая не
    пустая строка, включая символ ‘/’.

Например, вместо
int часто указывают slug, так URL становится
более информативным:

path('cats/<slug:cat>/', categories),

Изменим функцию представления:

def categories(request, cat):
    return HttpResponse(f"<h1>Статьи по категориям</h1>{cat}</p>")

И теперь в
качестве категории можно указывать не просто числа, а строку в виде слага,
например, так:

127.0.0.1:8000/cats/sport/

127.0.0.1:8000/cats/music/

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

Если по каким-то
причинам представленных типов для URL недостаточно, то в Django имеется функция

re_path()

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

re_path(r'^archive/(?P<year>[0-9]{4})/', archive),

Добавим функцию представления
archive:

def archive(request, year):
    return HttpResponse(f"<h1>Архив по годам</h1>{year}</p>")

И теперь мы
можем обращаться к этому URL, указывая ровно 4 цифры:

http://127.0.0.1:8000/archive/2020/

Если же указать
меньше или больше чисел, то шаблон URL не совпадет и возникнет
исключение 404 – страница не найдена. Вот так, достаточно гибко можно задавать
список URL для механизма
маршрутизации в Django.

Обработка GET и POST запросов

Те из вас, кто занимался
веб-разработкой знают, что структура URL-адреса может содержать
дополнительные параметры в виде GET-запроса. Например, вот так:

http://127.0.0.1:8000/?name=Gagarina&cat=music

или так:

127.0.0.1:8000/cats/music/?name=Gagarina&type=pop

И так далее. Здесь
у нас идет специальный разделитель – знак вопроса, после которого через
амперсанд перечисляются различные параметры в виде пар ключ-значение. Как можно
выделять такие значения и обрабатывать их в функциях представления? Для этого,
как раз и используется ссылка request на объект HttpRequest. Мы через нее
можем обратиться к специальному словарю:

request.GET

где и
сохраняются все эти данные:

def categories(request, cat):
    print(request.GET)
    return HttpResponse(f"<h1>Статьи по категориям</h1>{cat}</p>")

Например, по запросу:

127.0.0.1:8000/cats/music/?name=Gagarina&type=pop

мы увидим в
консоли значения:

<QueryDict:
{‘name’: [‘Gagarina’], ‘type’: [‘pop’]}>

Или, можем
сначала проверить: есть ли в словаре какие-либо данные и только потом выводить
их в консоль:

def categories(request, cat):
    if(request.GET):
        print(request.GET)
 
    return HttpResponse(f"<h1>Статьи по категориям</h1>{cat}</p>")

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

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

def categories(request, cat):
    if(request.POST):
        print(request.POST)
 
    return HttpResponse(f"<h1>Статьи по категориям</h1>{cat}</p>")

Обработка исключений при запросах к серверу

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

http://127.0.0.1:8000/aaa

то мы увидим
исключение 404 – страница не найдена. Такую информацию мы видим исключительно в
процессе отладки нашего сайта, когда глобальная константа DEBUG в пакете
конфигурации (файл settings.py) установлена в True.

Давайте
посмотрим, что произойдет, если временно перевести ее в значение False. Перейдем в
пакет конфигурации, откроем файл settings.py, найдем
константу DEBUG и присвоим ей
значение False. При запуске
тестового сервера у нас возникнет ошибка, что мы должны указать разрешенные
хосты. Так как мы сейчас используем хост 127.0.0.1, то его в виде строки и
укажем:

ALLOWED_HOSTS = ['127.0.0.1']

Теперь сервер
успешно запустился и при обновлении страницы мы уже видим более дружественную
страницу без лишней отладочной информации. Но как нам ее поправить, чтобы
отобразить более понятную информацию? Для этого в файле urls.py пакета
конфигурации можно переопределить обработчик исключения 404. Для этого ему
достаточно присвоить ссылку на функцию, которая и будет формировать ответ для
отсутствующих страниц, например, так:

handler404 = pageNotFound

Мы здесь
передаем ссылку на функцию pageNotFound, а саму функцию определим в приложении women:

def pageNotFound(request, exception):
    return HttpResponseNotFound('<h1>Страница не найдена</h1>')

Обратите
внимание, функция принимает два аргумента и возвращает ответ в виде экземпляра
класса HttpResponseNotFound, которому
передается HTML-страница,
отображаемая при неверных запросах. Если теперь мы обновим страницу, то увидим
заголовок «Страница не найдена».

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

Итак, мы
предполагаем, что функция pageNotFound будет вызываться всякий раз при
возникновении исключения 404. И это важный момент. Смотрите, если в какой-либо
другой функции представления сгенерировать это исключение, то будет
автоматическое перенаправление на функцию pageNotFound и пользователь увидит
все ту же страницу 404. Например, в функции archive мы сделаем
проверку:

from django.http import HttpResponse, HttpResponseNotFound, Http404
 
def archive(request, year):
    if(int(year) > 2020):
        raise Http404()
 
    return HttpResponse(f"<h1>Архив по годам</h1>{year}</p>")

Если год больше
2020-го, то генерируется исключение 404 как экземпляр класса Http404 и
выполняется перенаправление на функцию pageNotFound. Это нам позволяет
описывать логику отображения неверных запросов в одном месте программы – в
функции pageNotFound.

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

  • handler500 –
    ошибка сервера;

  • handler403 –
    доступ запрещен;

  • handler400 –
    невозможно обработать запрос.

Но все они
работают в боевом режиме при DEBUG = False. При отладке мы
увидим расширенную служебную информацию, помогающую исправлять ошибки при
разработке сайта. Более подробную информацию можно посмотреть на странице
русскоязычной документации:

https://djbook.ru/rel3.0/topics/http/views.html

Создание 301 и 302 редиректов

Очень часто при
развитии сайта некоторые его страницы переносятся на другой URL-адрес. И, чтобы
не потерять позиции этих страниц в поисковой выдаче, поисковым системам нужно
явно указать, что страница перемещена либо временно, либо постоянно на новый URL. Это делается с
помощью перенаправления с кодами:

  • 301 – страница
    перемещена на другой постоянный URL-адрес;

  • 302 – страница
    перемещена временно на другой URL-адрес.

В Django такие редиректы
достаточно просто выполняются с помощью функции:

django.shortcuts.redirect

Давайте для
примера сделаем перенаправление со страницы архива, если год больше 2000:

def archive(request, year):
    if(int(year) > 2020):
        return redirect('/')
 
    return HttpResponse(f"<h1>Архив по годам</h1>{year}</p>")

Здесь в качестве
первого параметра указывается страница, на которую происходит перенаправление,
в данном случае – это главная страница сайта. Если теперь выполнить запрос:

127.0.0.1:8000/archive/2021/

то мы попадем на
главную страницу с кодом перенаправления 302 (см. консоль). Если же нам нужно
указать постоянный редирект с кодом 301, то записывается дополнительный
параметр:

return redirect('/', permanent=True)

Параметр name функции path

Однако,
указывать в функции redirect, да и вообще где бы то ни было в
приложении конкретный URL-адрес (кроме их списка в коллекции urlpatterns)
– это порочная практика, или, как еще говорят – хардкодинг. Вместо этого
каждому шаблону пути можно присвоить свое уникальное имя и использовать его в
рамках всего проекта.

Давайте
определим имена для наших URL-запросов. Для этого перейдем в файл women/urls.py и в каждой
функции path пропишем
параметр name с уникальными
именами:

urlpatterns = [
    path('', index, name='home'),
    path('cats/<slug:cat>/', categories, name='cats'),
    re_path(r'^archive/(?P<year>[0-9]{4})/', archive, name='archive'),
]

Конечно, эти
имена вы можете выбрать и другие – это лишь пример. И далее, в функции redirect мы можем
выполнить перенаправление на главную страницу, указав имя home:

return redirect('home', permanent=True)

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

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

Видео по теме

status.py

418 I’m a teapot — Any attempt to brew coffee with a teapot should result in the error code «418 I’m a teapot». The resulting entity body MAY be short and stout.

— RFC 2324, Hyper Text Coffee Pot Control Protocol

Using bare status codes in your responses isn’t recommended. REST framework includes a set of named constants that you can use to make your code more obvious and readable.

from rest_framework import status
from rest_framework.response import Response

def empty_view(self):
    content = {'please move along': 'nothing to see here'}
    return Response(content, status=status.HTTP_404_NOT_FOUND)

The full set of HTTP status codes included in the status module is listed below.

The module also includes a set of helper functions for testing if a status code is in a given range.

from rest_framework import status
from rest_framework.test import APITestCase

class ExampleTestCase(APITestCase):
    def test_url_root(self):
        url = reverse('index')
        response = self.client.get(url)
        self.assertTrue(status.is_success(response.status_code))

For more information on proper usage of HTTP status codes see RFC 2616
and RFC 6585.

Informational — 1xx

This class of status code indicates a provisional response. There are no 1xx status codes used in REST framework by default.

HTTP_100_CONTINUE
HTTP_101_SWITCHING_PROTOCOLS

Successful — 2xx

This class of status code indicates that the client’s request was successfully received, understood, and accepted.

HTTP_200_OK
HTTP_201_CREATED
HTTP_202_ACCEPTED
HTTP_203_NON_AUTHORITATIVE_INFORMATION
HTTP_204_NO_CONTENT
HTTP_205_RESET_CONTENT
HTTP_206_PARTIAL_CONTENT
HTTP_207_MULTI_STATUS
HTTP_208_ALREADY_REPORTED
HTTP_226_IM_USED

Redirection — 3xx

This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request.

HTTP_300_MULTIPLE_CHOICES
HTTP_301_MOVED_PERMANENTLY
HTTP_302_FOUND
HTTP_303_SEE_OTHER
HTTP_304_NOT_MODIFIED
HTTP_305_USE_PROXY
HTTP_306_RESERVED
HTTP_307_TEMPORARY_REDIRECT
HTTP_308_PERMANENT_REDIRECT

Client Error — 4xx

The 4xx class of status code is intended for cases in which the client seems to have erred. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition.

HTTP_400_BAD_REQUEST
HTTP_401_UNAUTHORIZED
HTTP_402_PAYMENT_REQUIRED
HTTP_403_FORBIDDEN
HTTP_404_NOT_FOUND
HTTP_405_METHOD_NOT_ALLOWED
HTTP_406_NOT_ACCEPTABLE
HTTP_407_PROXY_AUTHENTICATION_REQUIRED
HTTP_408_REQUEST_TIMEOUT
HTTP_409_CONFLICT
HTTP_410_GONE
HTTP_411_LENGTH_REQUIRED
HTTP_412_PRECONDITION_FAILED
HTTP_413_REQUEST_ENTITY_TOO_LARGE
HTTP_414_REQUEST_URI_TOO_LONG
HTTP_415_UNSUPPORTED_MEDIA_TYPE
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
HTTP_417_EXPECTATION_FAILED
HTTP_422_UNPROCESSABLE_ENTITY
HTTP_423_LOCKED
HTTP_424_FAILED_DEPENDENCY
HTTP_426_UPGRADE_REQUIRED
HTTP_428_PRECONDITION_REQUIRED
HTTP_429_TOO_MANY_REQUESTS
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS

Server Error — 5xx

Response status codes beginning with the digit «5» indicate cases in which the server is aware that it has erred or is incapable of performing the request. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition.

HTTP_500_INTERNAL_SERVER_ERROR
HTTP_501_NOT_IMPLEMENTED
HTTP_502_BAD_GATEWAY
HTTP_503_SERVICE_UNAVAILABLE
HTTP_504_GATEWAY_TIMEOUT
HTTP_505_HTTP_VERSION_NOT_SUPPORTED
HTTP_506_VARIANT_ALSO_NEGOTIATES
HTTP_507_INSUFFICIENT_STORAGE
HTTP_508_LOOP_DETECTED
HTTP_509_BANDWIDTH_LIMIT_EXCEEDED
HTTP_510_NOT_EXTENDED
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED

Helper functions

The following helper functions are available for identifying the category of the response code.

is_informational()  # 1xx
is_success()        # 2xx
is_redirect()       # 3xx
is_client_error()   # 4xx
is_server_error()   # 5xx

The redirects app¶

Django comes with an optional redirects application. It lets you store
redirects in a database and handles the redirecting for you. It uses the HTTP
response status code 301 Moved Permanently by default.

Installation¶

To install the redirects app, follow these steps:

  1. Ensure that the django.contrib.sites framework
    is installed.
  2. Add 'django.contrib.redirects' to your INSTALLED_APPS setting.
  3. Add 'django.contrib.redirects.middleware.RedirectFallbackMiddleware'
    to your MIDDLEWARE setting.
  4. Run the command manage.py migrate.

How it works¶

manage.py migrate creates a django_redirect table in your database. This
is a lookup table with site_id, old_path and new_path fields.

The RedirectFallbackMiddleware
does all of the work. Each time any Django application raises a 404
error, this middleware checks the redirects database for the requested
URL as a last resort. Specifically, it checks for a redirect with the
given old_path with a site ID that corresponds to the
SITE_ID setting.

  • If it finds a match, and new_path is not empty, it redirects to
    new_path using a 301 (“Moved Permanently”) redirect. You can subclass
    RedirectFallbackMiddleware
    and set
    response_redirect_class
    to django.http.HttpResponseRedirect to use a
    302 Moved Temporarily redirect instead.
  • If it finds a match, and new_path is empty, it sends a 410 (“Gone”)
    HTTP header and empty (content-less) response.
  • If it doesn’t find a match, the request continues to be processed as
    usual.

The middleware only gets activated for 404s – not for 500s or responses of any
other status code.

Note that the order of MIDDLEWARE matters. Generally, you can put
RedirectFallbackMiddleware at the
end of the list, because it’s a last resort.

For more on middleware, read the middleware docs.

How to add, change and delete redirects¶

Via the admin interface¶

If you’ve activated the automatic Django admin interface, you should see a
“Redirects” section on the admin index page. Edit redirects as you edit any
other object in the system.

Via the Python API¶

class models.Redirect

Redirects are represented by a standard Django model,
which lives in django/contrib/redirects/models.py. You can access
redirect objects via the Django database API.
For example:

>>> from django.conf import settings
>>> from django.contrib.redirects.models import Redirect
>>> # Add a new redirect.
>>> redirect = Redirect.objects.create(
...     site_id=1,
...     old_path='/contact-us/',
...     new_path='/contact/',
... )
>>> # Change a redirect.
>>> redirect.new_path = '/contact-details/'
>>> redirect.save()
>>> redirect
<Redirect: /contact-us/ ---> /contact-details/>
>>> # Delete a redirect.
>>> Redirect.objects.filter(site_id=1, old_path='/contact-us/').delete()
(1, {'redirects.Redirect': 1})

Middleware¶

class middleware.RedirectFallbackMiddleware

You can change the HttpResponse classes used
by the middleware by creating a subclass of
RedirectFallbackMiddleware
and overriding response_gone_class and/or response_redirect_class.

response_gone_class

The HttpResponse class used when a
Redirect is not found for the
requested path or has a blank new_path value.

Defaults to HttpResponseGone.

response_redirect_class

The HttpResponse class that handles the redirect.

Defaults to HttpResponsePermanentRedirect.

When you build a Python web application with the Django framework, you’ll at some point have to redirect the user from one URL to another.

In this guide, you’ll learn everything you need to know about HTTP redirects and how to deal with them in Django. At the end of this tutorial, you’ll:

  • Be able to redirect a user from one URL to another URL
  • Know the difference between temporary and permanent redirects
  • Avoid common pitfalls when working with redirects

This tutorial assumes that you’re familiar with the basic building blocks of a Django application, like views and URL patterns.

Django Redirects: A Super Simple Example

In Django, you redirect the user to another URL by returning an instance of HttpResponseRedirect or HttpResponsePermanentRedirect from your view. The simplest way to do this is to use the function redirect() from the module django.shortcuts. Here’s an example:

# views.py
from django.shortcuts import redirect

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

Just call redirect() with a URL in your view. It will return a HttpResponseRedirect class, which you then return from your view.

A view returning a redirect has to be added to your urls.py, like any other view:

# urls.py
from django.urls import path

from .views import redirect_view

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

Assuming this is the main urls.py of your Django project, the URL /redirect/ now redirects to /redirect-success/.

To avoid hard-coding the URL, you can call redirect() with the name of a view or URL pattern or a model to avoid hard-coding the redirect URL. You can also create a permanent redirect by passing the keyword argument permanent=True.

This article could end here, but then it could hardly be called “The Ultimate Guide to Django Redirects.” We will take a closer look at the redirect() function in a minute and also get into the nitty-gritty details of HTTP status codes and different HttpRedirectResponse classes, but let’s take a step back and start with a fundamental question.

Why Redirect

You might wonder why you’d ever want to redirect a user to a different URL in the first place. To get an idea where redirects make sense, have a look at how Django itself incorporates redirects into features that the framework provides by default:

  • When you are not logged-in and request a URL that requires authentication, like the Django admin, Django redirects you to the login page.
  • When you log in successfully, Django redirects you to the URL you requested originally.
  • When you change your password using the Django admin, you are redirected to a page that indicates that the change was successful.
  • When you create an object in the Django admin, Django redirects you to the object list.

What would an alternative implementation without redirects look like? If a user has to log in to view a page, you could simply display a page that says something like “Click here to log in.” This would work, but it would be inconvenient for the user.

URL shorteners like http://bit.ly are another example of where redirects come in handy: you type a short URL into the address bar of your browser and are then redirected to a page with a long, unwieldy URL.

In other cases, redirects are not just a matter of convenience. Redirects are an essential instrument to guide the user through a web application. After performing some kind of operation with side effects, like creating or deleting an object, it’s a best practice to redirect to another URL to prevent accidentally performing the operation twice.

One example of this use of redirects is form handling, where a user is redirected to another URL after successfully submitting a form. Here’s a code sample that illustrates how you’d typically handle a form:

from django import forms
from django.http import HttpResponseRedirect
from django.shortcuts import redirect, render

def send_message(name, message):
    # Code for actually sending the message goes here

class ContactForm(forms.Form):
    name = forms.CharField()
    message = forms.CharField(widget=forms.Textarea)

def contact_view(request):
    # The request method 'POST' indicates
    # that the form was submitted
    if request.method == 'POST':  # 1
        # Create a form instance with the submitted data
        form = ContactForm(request.POST)  # 2
        # Validate the form
        if form.is_valid(): # 3
            # If the form is valid, perform some kind of
            # operation, for example sending a message
            send_message(
                form.cleaned_data['name'],
                form.cleaned_data['message']
            )
            # After the operation was successful,
            # redirect to some other page
            return redirect('/success/')  # 4
    else:  # 5
        # Create an empty form instance
        form = ContactForm()

    return render(request, 'contact_form.html', {'form': form})

The purpose of this view is to display and handle a contact form that allows the user to send a message. Let’s follow it step by step:

  1. First the view looks at the request method. When the user visits the URL connected to this view, the browser performs a GET request.

  2. If the view is called with a POST request, the POST data is used to instantiate a ContactForm object.

  3. If the form is valid, the form data is passed to send_message(). This function is not relevant in this context and therefore not shown here.

  4. After sending the message, the view returns a redirect to the URL /success/. This is the step we are interested in. For simplicity, the URL is hard-coded here. You’ll see later how you can avoid that.

  5. If the view receives a GET request (or, to be precise, any kind of request that is not a POST request), it creates an instance of ContactForm and uses django.shortcuts.render() to render the contact_form.html template.

If the user now hits reload, only the /success/ URL is reloaded. Without the redirect, reloading the page would re-submit the form and send another message.

Behind the Scenes: How an HTTP Redirect Works

Now you know why redirects make sense, but how do they work? Let’s have a quick recap of what happens when you enter a URL in the address bar of your web browser.

A Quick Primer on HTTP

Let’s assume you’ve created a Django application with a “Hello World” view that handles the path /hello/. You are running your application with the Django development server, so the complete URL is http://127.0.0.1:8000/hello/.

When you enter that URL in your browser, it connects to port 8000 on the server with the IP address 127.0.0.1 and sends an HTTP GET request for the path /hello/. The server replies with an HTTP response.

HTTP is text-based, so it’s relatively easy to look at the back and forth between the client and the server. You can use the command line tool curl with the option --include to have a look at the complete HTTP response including the headers, like this:

$ 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

As you can see, an HTTP response starts with a status line that contains a status code and a status message. The status line is followed by an arbitrary number of HTTP headers. An empty line indicates the end of the headers and the start of the response body, which contains the actual data the server wants to send.

HTTP Redirects Status Codes

What does a redirect response look like? Let’s assume the path /redirect/ is handled by redirect_view(), shown earlier. If you access http://127.0.0.1:8000/redirect/ with curl, your console looks like this:

$ 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

The two responses might look similar, but there are some key differences. The redirect:

  • Returns a different status code (302 versus 200)
  • Contains a Location header with a relative URL
  • Ends with an empty line because the body of the redirect response is empty

The primary differentiator is the status code. The specification of the HTTP standard says the following:

The 302 (Found) status code indicates that the target resource resides temporarily under a different URI. Since the redirection might be altered on occasion, the client ought to continue to use the effective request URI for future requests. The server SHOULD generate a Location header field in the response containing a URI reference for the different URI. The user agent MAY use the Location field value for automatic redirection. (Source)

In other words, whenever the server sends a status code of 302, it says to the client, “Hey, at the moment, the thing you are looking for can be found at this other location.”

A key phrase in the specification is “MAY use the Location field value for automatic redirection.” It means that you can’t force the client to load another URL. The client can choose to wait for user confirmation or decide not to load the URL at all.

Now you know that a redirect is just an HTTP response with a 3xx status code and a Location header. The key takeaway here is that an HTTP redirect is like any old HTTP response, but with an empty body, 3xx status code, and a Location header.

That’s it. We’ll tie this back into Django momentarily, but first let’s take a look at two types of redirects in that 3xx status code range and see why they matter when it comes to web development.

Temporary vs. Permanent Redirects

The HTTP standard specifies several redirect status codes, all in the 3xx range. The two most common status codes are 301 Permanent Redirect and 302 Found.

A status code 302 Found indicates a temporary redirect. A temporary redirect says, “At the moment, the thing you’re looking for can be found at this other address.” Think of it like a store sign that reads, “Our store is currently closed for renovation. Please go to our other store around the corner.” As this is only temporary, you’d check the original address the next time you go shopping.

As the name implies, permanent redirects are supposed to be permanent. A permanent redirect tells the browser, “The thing you’re looking for is no longer at this address. It’s now at this new address, and it will never be at the old address again.”

A permanent redirect is like a store sign that reads, “We moved. Our new store is just around the corner.” This change is permanent, so the next time you want to go to the store, you’d go straight to the new address.

Browsers behave similarly when handling redirects: when a URL returns a permanent redirect response, this response is cached. The next time the browser encounters the old URL, it remembers the redirect and directly requests the new address.

Caching a redirect saves an unnecessary request and makes for a better and faster user experience.

Furthermore, the distinction between temporary and permanent redirects is relevant for Search Engine Optimization.

Now you know that a redirect is just an HTTP response with a 3xx status code and a Location header.

You could build such a response yourself from a regular HttpResponse object:

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

This solution is technically correct, but it involves quite a bit of typing.

The HTTPResponseRedirect Class

You can save yourself some typing with the class HttpResponseRedirect, a subclass of HttpResponse. Just instantiate the class with the URL you want to redirect to as the first argument, and the class will set the correct status and Location header:

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

You can play with the HttpResponseRedirect class in the Python shell to see what you’re getting:

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

There is also a class for permanent redirects, which is aptly named HttpResponsePermanentRedirect. It works the same as HttpResponseRedirect, the only difference is that it has a status code of 301 (Moved Permanently).

You could use django.urls.reverse() to build a URL, but there is a more convenient way as you will see in the next section.

The redirect() Function

To make your life easier, Django provides the versatile shortcut function you’ve already seen in the introduction: django.shortcuts.redirect().

You can call this function with:

  • A model instance, or any other object, with a get_absolute_url() method
  • A URL or view name and positional and/or keyword arguments
  • A URL

It will take the appropriate steps to turn the arguments into a URL and return an HTTPResponseRedirect.
If you pass permanent=True, it will return an instance of HttpResponsePermanentRedirect, resulting in a permanent redirect.

Here are three examples to illustrate the different use cases:

  1. Passing a model:

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

    redirect() will call product.get_absolute_url() and use the result as redirect target. If the given class, in this case Product, doesn’t have a get_absolute_url() method, this will fail with a TypeError.

  2. Passing a URL name and arguments:

    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() will try to use its given arguments to reverse a URL. This example assumes your URL patterns contain a pattern like this:

    path('/product/<product_id>/', 'product_detail_view', name='product_detail')
    
  3. Passing a URL:

    from django.shortcuts import redirect
    
    def featured_product_view(request):
        return redirect('/products/42/')
    

    redirect() will treat any string containing a / or . as a URL and use it as redirect target.

The RedirectView Class-Based View

If you have a view that does nothing but returning a redirect, you could use the class-based view django.views.generic.base.RedirectView.

You can tailor RedirectView to your needs through various attributes.

If the class has a .url attribute, it will be used as a redirect URL. String formatting placeholders are replaced with named arguments from the 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'

The URL pattern defines an argument term, which is used in SearchRedirectView to build the redirect URL. The path /search/kittens/ in your application will redirect you to https://google.com/?q=kittens.

Instead of subclassing RedirectView to overwrite the url attribute, you can also pass the keyword argument url to as_view() in your urlpatterns:

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

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

You can also overwrite get_redirect_url() to get a completely custom behavior:

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)

This class-based view redirects to a URL picked randomly from .animal_urls.

django.views.generic.base.RedirectView offers a few more hooks for customization. Here is the complete list:

  • .url

    If this attribute is set, it should be a string with a URL to redirect to. If it contains string formatting placeholders like %(name)s, they are expanded using the keyword arguments passed to the view.

  • .pattern_name

    If this attribute is set, it should be the name of a URL pattern to redirect to. Any positional and keyword arguments passed to the view are used to reverse the URL pattern.

  • .permanent

    If this attribute is True, the view returns a permanent redirect. It defaults to False.

  • .query_string

    If this attribute is True, the view appends any provided query string to the redirect URL. If it is False, which is the default, the query string is discarded.

  • get_redirect_url(*args, **kwargs)

    This method is responsible for building the redirect URL. If this method returns None, the view returns a 410 Gone status.

    The default implementation first checks .url. It treats .url as an “old-style” format string, using any named URL parameters passed to the view to expand any named format specifiers.

    If .url is not set, it checks if .pattern_name is set. If it is, it uses it to reverse a URL with any positional and keyword arguments it received.

    You can change that behavior in any way you want by overwriting this method. Just make sure it returns a string containing a URL.

You could implement the functionality of RandomAnimalView from the example above with this simple function-based view:

from random import choice
from django.shortcuts import redirect

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

As you can see, the class-based approach does not provide any obvious benefit while adding some hidden complexity. That raises the question: when should you use RedirectView?

If you want to add a redirect directly in your urls.py, using RedirectView makes sense. But if you find yourself overwriting get_redirect_url, a function-based view might be easier to understand and more flexible for future enhancements.

Advanced Usage

Once you know that you probably want to use django.shortcuts.redirect(), redirecting to a different URL is quite straight-forward. But there are a couple of advanced use cases that are not so obvious.

Passing Parameters with Redirects

Sometimes, you want to pass some parameters to the view you’re redirecting to. Your best option is to pass the data in the query string of your redirect URL, which means redirecting to a URL like this:

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

Let’s assume you want to redirect from some_view() to product_view(), but pass an optional parameter 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

The code in this example is quite dense, so let’s follow it step by step:

  1. First, you use django.urls.reverse() to get the URL mapping to product_view().

  2. Next, you have to build the query string. That’s the part after the question mark. It’s advisable to use urllib.urlparse.urlencode() for that, as it will take care of properly encoding any special characters.

  3. Now you have to join base_url and query_string with a question mark. A format string works fine for that.

  4. Finally, you pass url to django.shortcuts.redirect() or to a redirect response class.

  5. In product_view(), your redirect target, the parameter will be available in the request.GET dictionary. The parameter might be missing, so you should use requests.GET.get('category') instead of requests.GET['category']. The former returns None when the parameter does not exist, while the latter would raise an exception.

Special Redirect Codes

Django provides HTTP response classes for the status codes 301 and 302. Those should cover most use cases, but if you ever have to return status codes 303, 307, or 308, you can quite easily create your own response class. Simply subclass HttpResponseRedirectBase and overwrite the status_code attribute:

class HttpResponseTemporaryRedirect(HttpResponseRedirectBase):
    status_code = 307

Alternatively, you can use the django.shortcuts.redirect() method to create a response object and change the return value. This approach makes sense when you have the name of a view or URL or a model you want to redirect to:

def temporary_redirect_view(request):
    response = redirect('success_view')
    response.status_code = 307
    return response

Pitfalls

Redirects That Just Won’t Redirect

The simplicity of django.shortcuts.redirect() can be deceiving. The function itself doesn’t perform a redirect: it just returns a redirect response object. You must return this response object from your view (or in a middleware). Otherwise, no redirect will happen.

But even if you know that just calling redirect() is not enough, it’s easy to introduce this bug into a working application through a simple refactoring. Here’s an example to illustrate that.

Let’s assume you are building a shop and have a view that is responsible for displaying a product. If the product does not exist, you redirect to the homepage:

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})

Now you want to add a second view to display customer reviews for a product. It should also redirect to the homepage for non-existing products, so as a first step, you extract this functionality from product_view() into a helper function 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})

Unfortunately, after the refactoring, the redirect does not work anymore.

The result of redirect() is returned from get_product_or_redirect(), but product_view() does not return it. Instead, it is passed to the template.

Depending on how you use the product variable in the product_detail.html template, this might not result in an error message and just display empty values.

Redirects That Just Won’t Stop Redirecting

When dealing with redirects, you might accidentally create a redirect loop, by having URL A return a redirect that points to URL B which returns a redirect to URL A, and so on. Most HTTP clients detect this kind of redirect loop and will display an error message after a number of requests.

Unfortunately, this kind of bug can be tricky to spot because everything looks fine on the server side. Unless your users complain about the issue, the only indication that something might be wrong is that you’ve got a number of requests from one client that all result in a redirect response in quick succession, but no response with a 200 OK status.

Here’s a simple example of a redirect loop:

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

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

This example illustrates the principle, but it’s overly simplistic. The redirect loops you’ll encounter in real-life are probably going to be harder to spot. Let’s look at a more elaborate example:

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() fetches all featured products, in other words Product instances with .featured set to True. If only one featured product exists, it redirects directly to product_view(). Otherwise, it renders a template with the featured_products queryset.

The product_view looks familiar from the previous section, but it has two minor differences:

  • The view tries to fetch a Product that is in stock, indicated by having .in_stock set to True.
  • The view redirects to featured_products_view() if no product is in stock.

This logic works fine until your shop becomes a victim of its own success and the one featured product you currently have goes out of stock. If you set .in_stock to False but forget to set .featured to False as well, then any visitor to your feature_product_view() will now be stuck in a redirect loop.

There is no bullet-proof way to prevent this kind of bug, but a good starting point is to check if the view you are redirecting to uses redirects itself.

Permanent Redirects Are Permanent

Permanent redirects can be like bad tattoos: they might seem like a good idea at the time, but once you realize they were a mistake, it can be quite hard to get rid of them.

When a browser receives a permanent redirect response for a URL, it caches this response indefinitely. Any time you request the old URL in the future, the browser doesn’t bother loading it and directly loads the new URL.

It can be quite tricky to convince a browser to load a URL that once returned a permanent redirect. Google Chrome is especially aggressive when it comes to caching redirects.

Why can this be a problem?

Imagine you want to build a web application with Django. You register your domain at myawesomedjangowebapp.com. As a first step, you install a blog app at https://myawesomedjangowebapp.com/blog/ to build a launch mailing list.

Your site’s homepage at https://myawesomedjangowebapp.com/ is still under construction, so you redirect to https://myawesomedjangowebapp.com/blog/. You decide to use a permanent redirect because you heard that permanent redirects are cached and caching make things faster, and faster is better because speed is a factor for ranking in Google search results.

As it turns out, you’re not only a great developer, but also a talented writer. Your blog becomes popular, and your launch mailing list grows. After a couple of months, your app is ready. It now has a shiny homepage, and you finally remove the redirect.

You send out an announcement email with a special discount code to your sizeable launch mailing list. You lean back and wait for the sign-up notifications to roll in.

To your horror, your mailbox fills with messages from confused visitors who want to visit your app but are always being redirected to your blog.

What has happened? Your blog readers had visited https://myawesomedjangowebapp.com/ when the redirect to https://myawesomedjangowebapp.com/blog/ was still active. Because it was a permanent redirect, it was cached in their browsers.

When they clicked on the link in your launch announcement mail, their browsers never bothered to check your new homepage and went straight to your blog. Instead of celebrating your successful launch, you’re busy instructing your users how to fiddle with chrome://net-internals to reset the cache of their browsers.

The permanent nature of permanent redirects can also bite you while developing on your local machine. Let’s rewind to the moment when you implemented that fateful permanent redirect for myawesomedjangowebapp.com.

You start the development server and open http://127.0.0.1:8000/. As intended, your app redirects your browser to http://127.0.0.1:8000/blog/. Satisfied with your work, you stop the development server and go to lunch.

You return with a full belly, ready to tackle some client work. The client wants some simple changes to their homepage, so you load the client’s project and start the development server.

But wait, what is going on here? The homepage is broken, it now returns a 404! Due to the afternoon slump, it takes you a while to notice that you’re being redirected to http://127.0.0.1:8000/blog/, which doesn’t exist in the client’s project.

To the browser, it doesn’t matter that the URL http://127.0.0.1:8000/ now serves a completely different application. All that matters to the browser is that this URL once in the past returned a permanent redirect to http://127.0.0.1:8000/blog/.

The takeaway from this story is that you should only use permanent redirects on URLs that you’ve no intention of ever using again. There is a place for permanent redirects, but you must be aware of their consequences.

Even if you’re confident that you really need a permanent redirect, it’s a good idea to implement a temporary redirect first and only switch to its permanent cousin once you’re 100% sure everything works as intended.

Unvalidated Redirects Can Compromise Security

From a security perspective, redirects are a relatively safe technique. An attacker cannot hack a website with a redirect. After all, a redirect just redirects to a URL that an attacker could just type in the address bar of their browser.

However, if you use some kind of user input, like a URL parameter, without proper validation as a redirect URL, this could be abused by an attacker for a phishing attack. This kind of redirect is called an open or unvalidated redirect.

There are legitimate use cases for redirecting to URL that is read from user input. A prime example is Django’s login view. It accepts a URL parameter next that contains the URL of the page the user is redirected to after login. To redirect the user to their profile after login, the URL might look like this:

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

Django does validate the next parameter, but let’s assume for a second that it doesn’t.

Without validation, an attacker could craft a URL that redirects the user to a website under their control, for example:

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

The website myawesomedjangowebapp.co might then display an error message and trick the user into entering their credentials again.

The best way to avoid open redirects is to not use any user input when building a redirect URL.

If you cannot be sure that a URL is safe for redirection, you can use the function django.utils.http.is_safe_url() to validate it. The docstring explains its usage quite well:

is_safe_url(url, host=None, allowed_hosts=None, require_https=False)

Return True if the url is a safe redirection (i.e. it doesn’t point to a different host and uses a safe scheme).
Always return False on an empty url.
If require_https is True, only ‘https’ will be considered a valid scheme, as opposed to ‘http’ and ‘https’ with the default, False. (Source)

Let’s look at some examples.

A relative URL is considered safe:

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

A URL pointing to another host is generally not considered safe:

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

A URL pointing to another host is considered safe if its host is provided in allowed_hosts:

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

If the argument require_https is True, a URL using the http scheme is not considered safe:

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

Summary

This wraps up this guide on HTTP redirects with Django. Congratulations: you have now touched on every aspect of redirects all the way from the low-level details of the HTTP protocol to the high-level way of dealing with them in Django.

You learned how an HTTP redirect looks under the hood, what the different status codes are, and how permanent and temporary redirects differ. This knowledge is not specific to Django and is valuable for web development in any language.

You can now perform a redirect with Django, either by using the redirect response classes HttpResponseRedirect and HttpResponsePermanentRedirect, or with the convenience function django.shortcuts.redirect(). You saw solutions for a couple of advanced use cases and know how to steer clear of common pitfalls.

If you have any further question about HTTP redirects leave a comment below and in the meantime, happy redirecting!

References

  • Django documentation: django.http.HttpResponseRedirect
  • Django documentation: django.shortcuts.render()
  • Django documentation: django.views.generic.base.RedirectView
  • RFC 7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content – 6.4 Redirection 3xx
  • CWE-601: URL Redirection to Untrusted Site (‘Open Redirect’)

[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

Я создал API, используя djangorestframework, но когда я запускаю его в терминале с помощью команды python manage.py runningerver, она показывает ошибку HTTP/1.0 301 Moved Permanentently. Это сообщение, которое оно показывает

Но здорово, когда я запускаю его в браузере, он отображает данные. Это результат в Chrome

Я хотел знать, что произошло в нем.

И в этом вызов POST также не работает.

Ниже приведен код:

serializers.py

from rest_framework import serializers
from snippets.models import Snippet

class SnippetSerializer(serializers.ModelSerializer):
            class Meta:
                model = Snippet
                fields = ('title','code',)

def create(self, validated_data):
    return Snippet.objects.create(**validated_data)


def update(self, instance, validated_data):
    instance.title = validated_data.get('title', instance.title)
    instance.code = validated_data.get('code', instance.code)
    instance.save()
    return instance

Views.py

from django.shortcuts import render
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


# Create your views here.


class JSONResponse(HttpResponse):
    def __init__(self,data,**kwargs):
        content = JSONRenderer().render(data)
        kwargs['content_type']='application/json'
        super(JSONResponse, self).__init__(content, **kwargs)

@csrf_exempt
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JSONResponse(serializer.data)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JSONResponse(serializer.data, status=201)
        return JSONResponse(serializer.errors, status=400)

@csrf_exempt
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JSONResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JSONResponse(serializer.data)
        return JSONResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

Models.py

from __future__ import unicode_literals

from django.db import models

# Create your models here.


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()


class Meta:
    ordering = ('created',)

418 I’m a teapot — Any attempt to brew coffee with a teapot should result in the error code «418 I’m a teapot». The resulting entity body MAY be short and stout.

— RFC 2324, Hyper Text Coffee Pot Control Protocol

Using bare status codes in your responses isn’t recommended. REST framework includes a set of named constants that you can use to make more code more obvious and readable.

from rest_framework import status

def empty_view(self):
    content = {'please move along': 'nothing to see here'}
    return Response(content, status=status.HTTP_404_NOT_FOUND)

The full set of HTTP status codes included in the status module is listed below.

For more information on proper usage of HTTP status codes see RFC 2616
and RFC 6585.

Informational — 1xx

This class of status code indicates a provisional response. There are no 1xx status codes used in REST framework by default.

HTTP_100_CONTINUE
HTTP_101_SWITCHING_PROTOCOLS

Successful — 2xx

This class of status code indicates that the client’s request was successfully received, understood, and accepted.

HTTP_200_OK
HTTP_201_CREATED
HTTP_202_ACCEPTED
HTTP_203_NON_AUTHORITATIVE_INFORMATION
HTTP_204_NO_CONTENT
HTTP_205_RESET_CONTENT
HTTP_206_PARTIAL_CONTENT

Redirection — 3xx

This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request.

HTTP_300_MULTIPLE_CHOICES
HTTP_301_MOVED_PERMANENTLY
HTTP_302_FOUND
HTTP_303_SEE_OTHER
HTTP_304_NOT_MODIFIED
HTTP_305_USE_PROXY
HTTP_306_RESERVED
HTTP_307_TEMPORARY_REDIRECT

Client Error — 4xx

The 4xx class of status code is intended for cases in which the client seems to have erred. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition.

HTTP_400_BAD_REQUEST
HTTP_401_UNAUTHORIZED
HTTP_402_PAYMENT_REQUIRED
HTTP_403_FORBIDDEN
HTTP_404_NOT_FOUND
HTTP_405_METHOD_NOT_ALLOWED
HTTP_406_NOT_ACCEPTABLE
HTTP_407_PROXY_AUTHENTICATION_REQUIRED
HTTP_408_REQUEST_TIMEOUT
HTTP_409_CONFLICT
HTTP_410_GONE
HTTP_411_LENGTH_REQUIRED
HTTP_412_PRECONDITION_FAILED
HTTP_413_REQUEST_ENTITY_TOO_LARGE
HTTP_414_REQUEST_URI_TOO_LONG
HTTP_415_UNSUPPORTED_MEDIA_TYPE
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
HTTP_417_EXPECTATION_FAILED
HTTP_428_PRECONDITION_REQUIRED
HTTP_429_TOO_MANY_REQUESTS
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE

Server Error — 5xx

Response status codes beginning with the digit «5» indicate cases in which the server is aware that it has erred or is incapable of performing the request. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition.

HTTP_500_INTERNAL_SERVER_ERROR
HTTP_501_NOT_IMPLEMENTED
HTTP_502_BAD_GATEWAY
HTTP_503_SERVICE_UNAVAILABLE
HTTP_504_GATEWAY_TIMEOUT
HTTP_505_HTTP_VERSION_NOT_SUPPORTED
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED

Понравилась статья? Поделить с друзьями:
  • Error 3000 neato
  • Error 300 radiodetection
  • Error 30 precor
  • Error 3 на стиральной машине что делать
  • Error 3 на стиральной машине candy что это значит