Руководство часть 7: Сессии
===========================

* Назад
* Обзор: Django
* Далее  (en-US)

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

| Требования: | Завершить изучение всех предыдущих разделов, включая Django Руководство Часть 6: Обобщённые отображения списков и детальной информации |
| --- | --- |
| Цель: | Понимать как применять сессии. |

Обзор
-----

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

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

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

Что такое сессии?
-----------------

Все взаимодействия между браузерами и серверами осуществляются при помощи протокола HTTP, который не сохраняет своё состояние (*stateless)*. Данный факт означает, что сообщения между клиентом и сервером являются полностью независимыми один от другого — то есть не существует какого-либо представления "последовательности", или поведения в зависимости от предыдущих сообщений. В результате, если вы хотите создать сайт, который будет отслеживать взаимодействие с клиентом (браузером), вам нужно реализовать это самостоятельно.

Сессии являются механизмом, который использует Django (да и весь остальной "Интернет") для отслеживания "состояния" между сайтом и каким-либо браузером. Сессии позволяют вам хранить произвольные данные браузера и получать их в тот момент, когда между данным браузером и сайтом устанавливается соединение. Данные получаются и сохраняются в сессии при помощи соответствующего "ключа".

Django использует куки (cookie), которые содержат специальный *идентификатор сессии,* который выделяет среди остальных, каждый браузер и соответствующую сессию. Реальные *данные* сессии, по умолчанию, хранятся в базе данных сайта (это более безопасно, чем сохранять данные в куки, где они могут быть уязвимы для злоумышленников). Однако, у вас есть возможность настроить Django так, чтобы сохранять данные сессий в других местах (кеше, файлах, "безопасных" куки). Но всё же хранение по умолчанию является хорошей и безопасной возможностью.

Подключение сессий
------------------

Сессии стали доступны автоматически в тот момент, когда мы создали скелет сайта (во второй части руководства).

Необходимые конфигурации выполняются в разделах `INSTALLED_APPS` и `MIDDLEWARE` файла проекта (**locallibrary/locallibrary/settings.py**), как показано ниже:


```python
INSTALLED_APPS = [
    ...
    'django.contrib.sessions',
    ....

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    ....
```

Применение сессий
-----------------

Вы можете получить доступ к переменной `session`, в соответствующем отображении, через параметр `request` (`HttpRequest` передаётся как первый аргумент в каждое отображение). Переменная сессии является связью с определённым пользователем (или, если быть более точным, связью с определённым *браузером*, который определяется при помощи идентификатора (id) сессии, получаемого из куки браузера).

Переменная (или поле) `session` является объектом-словарём, который служит для чтения и записи неограниченное число раз. С ним вы можете выполнять любые стандартные операции, включая очистку всех данных, проверку наличия ключа, циклы по данным и так далее. Большую часть времени вы будете тратить на обычные "словарные" операции - получения и установки значений.

Ниже представлены фрагменты кода, которые показывают вам как получать, задавать и удалять некоторые данные при помощи ключа "`my_car`", связанного с текущей сессией (браузером).

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


```python
# Получение значения сессии при помощи ключа(то есть, 'my\_car').
# Если такого ключа нет, то возникнет ошибка KeyError
my_car = request.session['my\_car']

# Получение значения сессии. Если значения не существует,
# то вернётся значение по умолчанию ('mini')
my_car = request.session.get('my\_car', 'mini')

# Передача значения в сессию
request.session['my\_car'] = 'mini'

# Удаление значения из сессии
del request.session['my\_car']
```


Данное API имеет другие методы, которые большей частью используются для управления куки, связанных с сессией. Например, существуют методы проверки того, что куки поддерживаются клиентским браузером, другие методы служат для установки и проверки предельных дат жизни куки, а также для очистки просроченных сессий из хранилища. Подробное описание API вы можете найти в разделе Как использовать сессии (Django docs).

Хранение данных сессии
----------------------

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


```python
# Данное присваивание распознается как обновление сессии
# и данные будут сохранены
request.session['my\_car'] = 'mini'
```


Если вы обновите информацию *внутри* данных сессии, тогда Django не распознает эти изменения и не выполнит сохранение данных (например, если вы изменили "`wheels`" внутри переменной "`my_car`", как показано ниже). В таких случаях вам надо явно указывать, что сессия была изменена.


```python
# Объект сессии модифицируется неявно.
# Изменения НЕ БУДУТ сохранены!
request.session['my\_car']['wheels'] = 'alloy'

# Явное указание, что данные изменены.
# Сессия будет сохранена, куки обновлены (если необходимо).
request.session.modified = True
```


**Примечание:** вы можете изменить поведение сессий таким образом, чтобы они записывали любое своё изменение в базу данных и отправляли куки, при каждом запросе, путём установки `SESSION_SAVE_EVERY_REQUEST = True`, в файле настроек проекта (**locallibrary/locallibrary/settings.py**).

Простой пример — получение числа визитов
----------------------------------------

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

Откройте **/locallibrary/catalog/views.py** и добавьте изменения, выделенных жирным, ниже.


```python
def index(request):
    ...

    num_authors=Author.objects.count()  # The 'all()' is implied by default.

    # Number of visits to this view, as counted in the session variable.
    num_visits=request.session.get('num\_visits', 0)
    request.session['num\_visits'] = num_visits+1

    # Render the HTML template index.html with the data in the context variable.
    return render(
        request,
        'index.html',
        context={'num\_books':num_books,'num\_instances':num_instances,'num\_instances\_available':num_instances_available,'num\_authors':num_authors,
            'num\_visits':num_visits}, # num\_visits appended
    )
```


В первую очередь мы получаем значение `'num_visits'` из сессии, возвращая 0, если оно не было установлено ранее. Каждый раз при получении запроса, мы увеличиваем данное значение на единицу и сохраняем его обратно в сессии (до следующего посещения данной страницы пользователем). Затем переменная `num_visits` передаётся в шаблон через переменную контекста `context`.

**Примечание:** Можно проверить наличие поддержки куки в браузере (для примера, смотрите Как использовать сессии), или разработать наш UI таким образом, чтобы это не имело значения.

Для показа значения переменной, из следующего фрагмента добавьте нижнюю строчку кода в ваш шаблон главной страницы сайта (**/locallibrary/catalog/templates/index.html**), в его нижний раздел "Dynamic content":


```django
<h2>Dynamic content</h2>

<p>The library has the following record counts:</p>
<ul>
  <li><strong>Books:</strong> {{ num\_books }}</li>
  <li><strong>Copies:</strong> {{ num\_instances }}</li>
  <li><strong>Copies available:</strong> {{ num\_instances\_available }}</li>
  <li><strong>Authors:</strong> {{ num\_authors }}</li>
</ul>

<p>
  You have visited this page {{ num\_visits }}{% if num\_visits == 1 %} time{%
 else %} times{% endif %}.
</p>
```


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

Итоги
-----

Вы узнали как применять сессии для улучшения взаимодействие с *анонимными* пользователями.

В наших следующих статьях мы рассмотрим фреймворк аутентификации и авторизации (разрешение доступа, permission), и покажем вам как поддерживать пользовательские аккаунты.

Смотрите также
--------------

* Как использовать сессии (Django docs)
* Назад
* Обзор: Django
* Далее