Закрыть

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

Цитата дня

1. Модель покупателя

6 октября 2016 г. 8:47

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

Этот подход хорош для веб-сайтов, которые работают на CMS (Content Management System), или для Блогов, где только избранной группе пользователей персонала должен быть разрешен доступ. Этот подход также работает для веб-сервисов, таких как социальные сети или интранет-приложения, где посетители должны пройти аутентификацию с самого начала своей сессии.

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

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

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

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

Однако, так как такой подход является непереносимым во всех приложениях на основе Django, djangoSHOP вводит новую модель базы данных - модель Customer, которая расширяет существующую модель пользователя.

1.1. Свойства модели Покупателя (Customer model)

Модель Покупатель имеет связь 1:1 с существующей моделю User, а это значит, что для каждого клиента, всегда существует один и только один объект пользователя. Такой подход позволяет нам сделать несколько вещей.

Встроенная модель пользователя может быть выгружена и заменена на другую реализацию. Такая альтернативная реализация имеет небольшое ограничение. Она должна наследоваться от django.contrib.auth.models.AbstractBaseUser и от django.contrib.auth.models.PermissionMixin. Она должна также определить все поля, которые доступны в модели по умолчанию в django.contrib.auth.models.User.

Установив is_active флаг = False, мы можем создать гостей внутри модели User. Гости не могут войти в систему, они не могут сбросить пароль, и, следовательно, можно рассматривать их как "материализованных" анонимных пользователей.

Наличие гостей с записью в базе данных,дает нам еще одно преимущество: с помощью ключа сессии посетителя сайта в качестве имени пользователя объекта пользователя, можно установить связь между объектом пользователя в базе данных с иным анонимным посетителем. Это также позволяет моделям Корзину и Заказа всегда ссылаться к модели пользователя, так как они[модели Корзины и Заказа] не должны заботиться о том, аутентифицировался ли определенный пользователь или нет. Также поддерживается простой рабочий процесс, когда анонимный пользователь решает зарегистрироваться и аутентифицироваться в будущем.

1.2. Добавление модели Покупатель в наше приложение

Почти все модели в djangoSHOP, в том числе и сама модель Покупатель, использует Отложенный паттерн модели (Deferred Model Pattern). Это означает, что проект Django отвечает за материализацию этой модели и дополнительно позволяет продавцу добавлять произвольные поля для своей модели Покупатель. Например, варианты номер телефона, дату рождения, логическое поле "должен ли клиент получать рассылку новостей", его статус относительно скидок и т. д.

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

from shop.models.defaults.customer import Customer

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

from shop.models.customer import BaseCustomer

class Customer(BaseCustomer):
    birth_date = models.DateField("Date of Birth")
    # other customer related fields

1.2.1. Настройка Middleware (промежуточного слоя)

Модель Покупатель создаётся автоматически каждый раз, когда посетитель заходит на сайт. Всякий раз, когда внутренний Django AuthenticationMiddleware добавляет AnonymousUser к объекту запроса, то CustomerMiddleware в djangoSHOP добавляет VisitingCustomer к объекту запроса. Ни AnonymousUser, ни VisitingCustomer не хранятся в базе данных.

Всякий раз, когда AuthenticationMiddleware добавляет подтверждение пользователя к объекту запроса, то CustomerMiddleware в djangoSHOP добавляет подтверждение клиента к объекту запроса. Если связанный Покупатель пока ещё не существует, то CustomerMiddleware его создает.

Поэтому добавьте CustomerMiddleware после AuthenticationMiddleware в settings.py:

MIDDLEWARE_CLASSES = (
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'shop.middleware.CustomerMiddleware',
    ...
)

1.2.2. Настройка контекст процессора (Context Processors)

Кроме того, некоторые шаблоны, возможно, потребуют доступ к объекту клиента через RequestContext. Поэтому добавьте этот контекст процессора к settings.py.

TEMPLATE_CONTEXT_PROCESSORS = (
    ...
    'shop.context_processors.customer',
    ...
)

1.2.3. Детали реализации

Модель Покупатель имеет не нулевую 1:1 связь с моделью User. Поэтому каждый покупатель ассоциируется с одним конкретным пользователем. Например, доступ к хешированному паролю может быть получен через customer.user.password. Некоторые общие поля и методы из модели User, такие как first_name, last_name, email, is_anonymous() и is_authenticated() доступны напрямую, когда работаешь с объектом Покупатель. Сохранение объекта типа Покупатель также вызывает метод save() из ассоциированной модели User.

Другое направление - доступ к модели Покупатель из User - не всегда работает. Доступ к атрибуту не получится, если соответствующий объект покупателя потерян, другими словами, если не имеется обратной связи от Покупателя к данному объекту User.

>>> from django.contrib.auth import get_user_model
>>> user = get_user_model().create(username='bobo')
>>> print user.customer.salutation
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "django/db/models/fields/related.py", line 206, in __get__
    self.related.get_accessor_name()))
DoesNotExist: User has no customer.

Это может случится, когда объект User добавлен вручную или через другое Django приложение.

Во время запросов базы данных djangoSHOP всегда выполняет INNER JOIN между таблицей Customer и User. Поэтому лучше запросить пользователя через объект клиента, а не наоборот.

1.2.4. Анонимные пользователи и Посетители-покупатели

Большинство запросов на наш сайт будет содержать анонимный характер. Они не будут отправлять куки, содержащие session-id, и сервер не будет выделять специальное место под сессию. Милдварь добавляет объект VisitingCustomer в request, ассоциируя с объектом AnonymousUser. Эти два объекта не храняться внутри базы данных.

Всякий раз, когда анонимный пользователь/посетитель-покупатель добавляет свой первый товар в корзину, djangoSHOP конкретизирует объект пользователя в базе данных и связывает его с объектом клиента. Такой покупатель рассматриваестся как "незарегистрированный и invokingcustomer.is_authenticated() вернёт False; здесь связанная с ним модель Пользователь неактивен и не имеет пароль.

1.2.5. Гости и зарегистрированные пользователи

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

1.2.6. Избавление критики

Некоторые скажут, что добавляя незарегистрированных и "гостевых" пользователей в таблицу пользователей в бд - это анти-паттерн или хак. Но какие есть альтернативы?

Мы могли бы хранить корзину или анонимных пользователей в хранилище сессий. Такой способ использовался в djangoSHOP до версии 0.2 (включая). Однако это вынуждало хранить две разные модели в корзине: одна для сессий, а другая для связи. Это очень не практично, особенно если модель корзины должна иметь возможность быть переопределённой собственной реализацией продавца.

Мы могли бы ассоциировать каждые модели корзины с session-id. Это могло бы потребовать дополнительное поле, которое было бы NULL для аутентифицированных покупателей. Хотя теоретически это возможно, но потребуется много кода, который выполняет различие между анонимными и аутентифицированными покупателями. Поскольку цель этого программного обеспечения - оставаться простым, поэтому эта идея была отклонена.

Мы могли бы хранить первичный ключ каждой корзины в сессии, связанной с анонимным пользователем/покупателем. Но было бы очень трудно найти корзины с истекшим сроком годности, потому что мы должны были бы перебрать все корзины и для каждой корзины мы должны перебрать все сеансы, чтобы проверить, соответствуют ли первичные ключи. Помните, что не существует такого понятия, как OUTER JOIN между сессиями и таблицами базы данных.

Мы могли бы создать объект Покупателя, который зависит от Пользователя. Так вместо наличия OneToOneField(AUTH_USER_MODEL) в модели Покупатель, мы имели бы 1:1 связь с внешним ключом, который может иметь NULL. Это требовало бы дополнительного поля в хранении session-id в моделе Покупатель. Это также требовало бы дополнительное поле email, если мы хотим оставлять "гостевых" покупателей как анонимных пользователей - что они, действительно, есть, только не могут аутентифицироваться. Помимо дублирования поля этот подход также потребует некоторого кода для того, чтобы различать непризнанных, "гостевых" и зарегистрированных клиентов. В дополнение к этому, бэкенд администрация потребует двух четко определенных взглядов, один для модели Покупатель и один для модели Пользователь.

1.3. Аутентификация по электронной почте

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

Эта модель пользователя практически идентична существующей модели пользователя, которую можно найти в django.contrib.auth.models.py. Разница заключается в том, что она использует поле Электронная почта, а не имя пользователя для поиска учетных данных. Чтобы активировать эту альтернативную модель пользователя, добавьте альтернативное приложение аутентификации settings.py проекта:

INSTALLED_APPS = (
    'django.contrib.auth',
    'email_auth',
    ...
)
Замечание:
Эта альтернативная модель Пользователь использует такую же таблицу auth_user в базе данных как использует модель Пользователь по умолчанию в Django. Поля альтернативной модели совместимы с встроенной модели и, следовательно, альтернативное приложение можно будет добавить позже к существующему проекту Django.

1.3.1. Оговорка при использовании альтернативной модели пользователя

Сообразительный читатель мог заметить, что в email_auth.models.User поле Электронная почта не объявлено как уникальное. Это, кстати, вызывает Джанго жаловаться во время запуска с:

WARNINGS:
email_auth.User: (auth.W004) 'User.email' is named as the 'USERNAME_FIELD', but it is not unique.
    HINT: Ensure that your authentication backend(s) can handle non-unique usernames.

Это предупреждение можно отключить, сделав его молчаливым SILENCED_SYSTEM_CHECKS = ['auth.W004'] в settings.py.

И тому есть две причины:

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

Во-вторых, уникальность требуется только для пользователей, которые на самом деле могут войти в систему. Гостевые пользователи, с другой стороны, не могут войти в систему, но они могут когда-нибудь вернуться. Имея уникальное поле электронной почты, Django приложение email_auth будет блокировать их и гости будут иметь возможность купить только один раз, но не во второй раз - а этого мы, конечно, не хотим!

Поэтому djangoSHOP предлагает два настраиваемых параметра:

  • Покупатели могут заявлять о себе в качестве гостей каждый раз, когда они что-то покупают. Это значение по умолчанию, но позволяет иметь неуникальные адреса электронной почты в базе данных.
  • Покупатели могут заявить о себе в качестве гостей в первый раз, когда они что-то покупают. Если когда-нибудь они вернутся на сайт, чтобы купить второй раз, они будут признаны как вернувшиеся клиенты и должны будут использовать форму для сброса пароля. Эта конфигурация активируется установкой SHOP_GUEST_IS_ACTIVE_USER = True. Кроме того, она позволяет нам установить ограничение уникальности на поле электронной почты.

Замечание: Поле Электронная почта от встроенной пользовательской модели Django имеет максимальную длину 75 символов. Этого достаточно для большинства случаев использования, но нарушает RFC-5321, который требует 254 символов. Альтернативная реализация использует правильную максимальную длину.

1.3.2. Администрирование Пользователей и Покупателей

Для моделей Покупатель и Пользователь можно повторно использовать те же самые сгенерированные Django административные интерфейсы. Для этого нужно импортировать и зарегистрировать бэкенд пользователя внутри admin.py:

from django.contrib import admin
from shop.admin.customer import CustomerProxy, CustomerAdmin

admin.site.register(CustomerProxy, CustomerAdmin)

Бэкенд администратора перерабатывает встроенный django.contrib.auth.admin.UserAdmin, и дополняет его, добавив модель Покупателя как StackedInlineAdmin на верхней части страницы. Поступая таким образом, мы можем изменять поля покупателя и пользователя на той же странице.

1.4. Сводная информация для Покупателя и Пользователя

Данная таблица подводит различие между Django моделью Пользователь [1] и моделью Shop Покупатель:

Shop’s Customer

Shop Покупатель

Model Django’s User Model

Django модель Пользователь

Active Session

Активация сессии

VisitingCustomer объект AnonymousUser объект Нет
Непризнанный Покупатель Неактивный User объект без пароля Да, но не залогинен
Покупатель признан как гость [2] Неактивный User объект с валидной эл. почтой, но без пароля Да, но не залогинен
Покупатель признан как гость [3] Активный User объект с непригодным паролем, но имеет возможность сбросить пароль Да, но не залогинен
Зарегистрированный Покупатель Активный User с валидной эл. почтой, знающий пароль, приветствие (опционально), фамилию и имя и т. д. Да, залогинен, с помощью Django бэкенда аутентификации

[1] или другая альтернативная модель User, установленная в переменной AUTH_USER_MODEL.

[2] если SHOP_GUEST_IS_ACTIVE_USER = False (по умолчанию).

[3] если SHOP_GUEST_IS_ACTIVE_USER = True.

1.4.1. Управление Покупателями

DjangoSHOP поставляется со специальной командой, которая информирует продавца о состоянии покупателей. В папке проекта, вызовите в командной строке:

./manage.py shop_customers
Customers in this shop: total=20482, anonymous=17418, expired=10111, active=1068, guests=1997, registered=1067, staff=5.

Читаются эти числа так:

  • Анонимные покупатели - это те, кто добавил хотя бы один товар в корзину, но прошёл процесс оформления заявки.
  • С истёкшим сроком покупатели являются подмножеством анонимных покупателей, чья сессия уже истекли.
  • Различие между гостем и зарегистрированным покупателем объяснено в таблице выше.

1.4.1.1. Удаление с истёкшим сроком покупателей

Вызов команды в коммандной строке:

./manage.py shop_customers --delete-expired

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

Оцените статью

0 из 5 (всего 0 оценок)

Поля, отмеченные звёздочкой ( * ) , являются обязательными.

Спасибо за ваш отзыв!

Автор перевода

Права на использование данной статьи, расположенной на настоящей странице http://vivazzi.ru/django-shop/customer-model/:

Разрешается копировать статью с указанием её автора и ссылки на оригинал без использования параметра rel="nofollow" в теге <a>. Использование:

Автор статьи: Мальцев Артём
Ссылка на статью: <a href="http://vivazzi.ru/django-shop/customer-model/">http://vivazzi.ru/django-shop/customer-model/</a>

Подробнее: Правила использования сайта

Вам нужно саморазвиваться или вы хотите зарабатывать деньги?

Или вы ищите хорошие IT сервисы или книги? Сохраните свое время и взгляните на мою подборку рекомендаций, которыми постоянно пользуюсь.
Посмотреть рекомендации

Комментариев: 0

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

Чтобы оставить комментарий от своего имени войдите или зарегистрируйтесь обычным способом или через социальные сети:

Отправить

На данный момент нет специального поиска, поэтому я предлагаю воспользоваться обычной поисковой системой, например, Google, добавив "vivazzi" после своего запроса.

Попробуйте