Не секрет, что работу с профилями пользователей в Django иначе как несчастьем не назовёшь. Все мы сталкивались с монолитностью модели
Извращаться приходилось всем: не только пользователям джанги, но и самим её core-разработчикам. Помните, например, как в Django 1.2 внезапно стало возможно использовать в поле
Нам, простым пользователям, тоже жилось несладко. Для того, чтобы изменить профиль пользователя, добавив ему какие-нибудь интересных полей — обычная, казалось бы, вещь, да? — приходилось действовать разными способами.
Ребята, подарившие миру легендарный sorl.thumbnail, очередной раз отличились и произвели на счет ещё одну убойную штуку. Встречайте: django-primate, приложение, которое используя методы манкипатчинга (приматы и обезьяны, чуете корреляцию?) позволяют очень просто превратить вашу собственную модель в
Начать использовать достаточно легко. Сначала надо поставить django-primate. Можно с PyPI:
… а можно и последнюю версию из их репозитория:
Вызывать патч нужно при старте. Создатели рекомендуют применять его в
теперь остаётся только указать в
… и можно начинать придумывать модели:
Теперь каждый раз, когда какой-либо компонент проекта будет обращаться к
По умолчанию, модель пользователя django-primate имеет следующие отличия от
В остальном же модель пользователя primateгуляет как утка, плавает как утка и крякает как утка ведёт себя очень похоже на джанговский первоисточник.
Разумеется, можно использовать абсолютно любой набор полей, но в этом случае есть риск, что возникнет несовместимость между сторонними приложениями, так что поля
Ещё один момент: для совместимости с джангой, primate пропачит модель так, что её
не создаёт никаких миграций.
Впрочем, все желающие могут ознакомиться с README, который я оочень вольно перевёл. Если я что-то упустил, ошибся или допустил неточность, пишите в комментариях или делитесь своими соображениями по поводу.
Спасибо за внимание :)
auth.User
, неадкеватным набором полей у неё, а также всеми теми ухищрениями, к которым приходилось прибегать. Извращаться приходилось всем: не только пользователям джанги, но и самим её core-разработчикам. Помните, например, как в Django 1.2 внезапно стало возможно использовать в поле
username
символы собаки (@) и точки? Знаете зачем? Чтобы в качестве логинов можно было использовать адреса e-mail.Нам, простым пользователям, тоже жилось несладко. Для того, чтобы изменить профиль пользователя, добавив ему какие-нибудь интересных полей — обычная, казалось бы, вещь, да? — приходилось действовать разными способами.
- Взять, к примеру, наследование. Требовалось создать собственную модель, унаследованную от
auth.User
…
# models.py from django.db import models from django.contrib.auth.models import User class MyUser(User): birthday = models.DateField()
… и написать миддльварь, которая бы подменяла класс дляrequest.user
. Или не миддльварь, аAUTHENTICATION_BACKEND
. Неважно :)
Плюсом схемы было то, что наш, «клиентский» код (то бишь код проектов) не усложнялся, мы просто работали с нашим пользователем, как с обычным джанговским.
Основной минус такой схемы то, что при наследовании моделей заполняется не одна таблица в БД, а две: исходная джанговскаяdjango_auth
и нашаourproj_user
, в которой есть внешний ключ наdjango_auth
. Да, наследование моделей в Django — это всего лишьOneToOneField
с какими-то дополнительными атрибутами. Захотите использовать — имейте в виду.
- Не менее известный костыль, предложенный самими создателями Django — так называемая модель профиля. Нам предлагалось создать модель
Profile
со связью один-к-одному наauth.User
…
# models.py from django.db import models from django.contrib.auth.models import User class Profile(models.Model): user = models.OneToOneField(User) birthday = models.DateField()
… а потом добавить в settings нечто такое:
AUTH_PROFILE_MODULE = 'accounts.Profile'
После этого в клиентском коде мы могли работать с профилем:
profile = request.user.get_profile() profile.birthday = datetime.today() profile.save()
Плюс у такой схемы — не знаю :) может, любой набор любых полей с любым именем? С минусами всё прозрачнее:
- сложность поддержки: у нас теперь не один объект для редактирования, а два. Надо не забыть, что дату рождения мы меняем у
profile
, а, к примеру, пароль уuser
. Немудрено и запутаться.
- нецелевая растрата ресурсов: каждое обращение к
get_profile()
вызывает запрос к БД. На страницах, где инстанс пользователя всего один (редактирование, например) это не страшно. Если же такая штука будет, например, в комментариях, результат будет плачевен. Разумеется,select_related()
, как вы понимаете, не спасёт, поскольку неUser
зависит отProfile
, а наоборот.
- а ещё всё надо руками делать! Создание модели
User
не означает, что автоматом создастся связанная модельProfile
. Зато при обращении кget_profile()
для вновь созданного юзера вылетит исключение — вот тут сомневаться не приходится. И хоть эта беда лечится в несколько строк простейшим сигналом,
# profile.models from django.db import models from django.contrib.auth.models import User class Profile(models.Model): 'что-нибудь хорошее' def create_profile(sender, **kwargs): if kwargs['created']: Profile.objects.create(user=kwargs['instance']) models.signals.post_save.connect(create_profile, sender=User)
всё же раздражает необходимость её «ручного» решения.
- сложность поддержки: у нас теперь не один объект для редактирования, а два. Надо не забыть, что дату рождения мы меняем у
- Манкипатчинг, то есть изменение поведения программы без переписывания кода. После инициализации приложения в каком-нибудь месте проекта (как правило, в корневом
urls
,settings
илиmodels
специально заведённого приложении) писали код, модифицирующий нашего Юзера:
# monkey_patching.models from django.db import models from django.contrib.auth.models import User User.add_to_class('birthday', models.DateField() )
Плюс, как и при наследовании, лёгкость в клиентском коде. Минусы — неочевидность. С магией, как известно, нужно обращаться очень осторожно, ведь можно невзначай переопределить какую-нибудь штуку, а всплывёт совсем в другом месте. C другой стороны, если очень осторожно, то почему бы и нет?
Кстати, об обезьянах...
Ребята, подарившие миру легендарный sorl.thumbnail, очередной раз отличились и произвели на счет ещё одну убойную штуку. Встречайте: django-primate, приложение, которое используя методы манкипатчинга (приматы и обезьяны, чуете корреляцию?) позволяют очень просто превратить вашу собственную модель в
auth.User
. То есть, по-русски говоря, составлять профиль из нужных полей.Начать использовать достаточно легко. Сначала надо поставить django-primate. Можно с PyPI:
pip install django-primate
… а можно и последнюю версию из их репозитория:
pip install -e git+https://github.com/aino/django-primate.git#egg=django-primate
Вызывать патч нужно при старте. Создатели рекомендуют применять его в
manage.py
#!/usr/bin/env python
from django.core.management import setup_environ, ManagementUtility
import imp
try:
imp.find_module('settings') # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write(
"Error: Can't find the file 'settings.py' in the directory "
"containing %r. It appears you've customized things.\nYou'll have to "
"run django-admin.py, passing it your settings module.\n" % __file__
)
sys.exit(1)
import settings
if __name__ == "__main__":
setup_environ(settings)
import primate
primate.patch()
ManagementUtility().execute()
теперь остаётся только указать в
settings
класс модели, который мы хотим использоватьAUTH_USER_MODEL = 'users.models.User'
… и можно начинать придумывать модели:
# users.models
from django.db import models
from primate.models import UserBase, UserMeta
class User(UserBase):
__metaclass__ = UserMeta
birthday = models.DateField()
# На что фантазии хватит?
Теперь каждый раз, когда какой-либо компонент проекта будет обращаться к
django.contrib.auth.models.User
, он будет получать модель users.models.User
. Обратное тоже верно. Админка пропатчится автоматом, никаких специальных действий для её подключения производить не надо.По умолчанию, модель пользователя django-primate имеет следующие отличия от
auth.User
:- Убраны поля
first_name
иlast_name
, зато добавленоname
; - Максимальная длина поля
username
составляет 50 символов (вauth.User
было 30) - Полю
email
добавлен уникальный индекс - Метод
get_profile
возвращаетself
, так что можно не опасаться за код, использующийuser.get_profile()
В остальном же модель пользователя primate
Разумеется, можно использовать абсолютно любой набор полей, но в этом случае есть риск, что возникнет несовместимость между сторонними приложениями, так что поля
username
, password
и email
лучше не переименовывать.Ещё один момент: для совместимости с джангой, primate пропачит модель так, что её
app_label
станет не users
(как в примере), а auth
. Это особо касается пользователей South, которые могут некоторое время не понимать, почему./manage.py schemamigration users --auto
не создаёт никаких миграций.
Впрочем, все желающие могут ознакомиться с README, который я оочень вольно перевёл. Если я что-то упустил, ошибся или допустил неточность, пишите в комментариях или делитесь своими соображениями по поводу.
Спасибо за внимание :)