Для Django <=1.5. С 1.6 есть встроенный похожий механизм.
По материалам http://scottbarnham.com/blog/2008/08/21/extending-the-django-user-model-with-inheritance/
Дополнительные поля для модели пользователя
Большинство Django проектов нуждаются в хранении дополнительной информации о каждом пользователе.
Старый способ: Профайл пользователя
Раньше создавалась модель для профайла пользователя, которая ассоциировалась как один-к-одному с моделью пользователя.
Модель:
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='profile')
timezone = models.CharField(max_length=50, default='Europe/London')
Конфигурация:
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
Использование:
profile = request.user.get_profile()
print profile.timezone
Данный подход отлично работал, но требовал дополнительного обращения к базе данных для любого запроса, использующего профайл пользователя (он, конечно, кэшировался в процессе выполнения запроса, таким образом каждое обращение к get_profile()
не приводило к выполнению запроса). Дополнительно к этому, информация о пользователе располагалась в двух отдельных моделях, что требовало её согласованного обновления в них.
Новый способ: Наследование моделей
Частью огромной работы, проделанной в queryset-refactor товарищем Malcolm и остальными, явилось поддержка Django наследования моделей.
Начиная с ревизии 7477 (26 апреля 2008) ваши классы моделей могут наследоваться от любой существующей модели. Дополнительные поля сохраняются в отдельной таблице, которая подключается к таблице основной модели. Когда вы запрашиваете данные из своей модели, запрос использует JOIN
для получения полей из неё и из базовой модели.
Наследование от User
Вместо того, чтобы создать класс для профайла пользователя, почему бы не унаследовать класс от стандартного User
и добавить несколько полей?
from django.contrib.auth.models import User, UserManager
class CustomUser(User):
"""User with app settings."""
timezone = models.CharField(max_length=50, default='Europe/London')
# Use UserManager to get the create_user method, etc.
objects = UserManager()
После этого, каждый экземпляр CustomerUser
будет обладать стандартными для User
полями и методами, одновременно с наличием наших дополнительных полей и методов. Неплохо, да?
Мы добавим UserManager
в качестве менеджера, получая таким образом доступ к стандартным методам. Например, для создания пользователя можно просто сделать так:
user = CustomUser.objects.create(...)
Если мы просто создадим пользователя через класс User
, то мы не получим создание записи в таблице CustomUser
. Создание пользователя необходимо производить через производный класс. Если модель CustomUser
не содержит дополнительных обязательных полей, мы можем исправить создание модели User
(например, при выполнении команды createsuperuser
):
from django.db.models.signals import post_save
def create_custom_user(sender, instance, created, **kwargs):
if created:
values = {}
for field in sender._meta.local_fields:
values[field.attname] = getattr(instance, field.attname)
user = CustomUser(**values)
user.save()
post_save.connect(create_custom_user, User)
Вы можете работать с классом User
, просто в этом случае у вас не будет доступа к новым полям и методам, которые предоставляет класс CustomUser
.
Получение класса CustomUser
по умолчанию
Но есть одна проблема. При обращении к request.user
вы получаете доступ к экземпляру класса User
, а не CustomUser
, таким образом не получая доступа к дополнительным полям.
Всё, что нам требуется - Django должна незаметно получать экземпляр CustomUser
. И этого можно добиться довольно легко.
Пользователи получаются от модуля аутентификации
Стандартный модуль (backend) аутентификации получает модель User
из базы данных, проверяя пароль на корректность, когда возращает экземпляр User
. Вы можете написать свой собственный модуль аутентификации, например, для проверки пароля и логина относительно другого источника данных, или для использования адреса электронной почты вместо логина.
В нашем случае, мы можем использовать модуль аутентификации для возвращения экземпляра CustomUser
вместо User
.
Файл auth_backends.py
:
from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_model
class CustomUserModelBackend(ModelBackend):
def authenticate(self, username=None, password=None):
try:
user = self.user_class.objects.get(username=username)
if user.check_password(password):
return user
except self.user_class.DoesNotExist:
return None
def get_user(self, user_id):
try:
return self.user_class.objects.get(pk=user_id)
except self.user_class.DoesNotExist:
return None
@property
def user_class(self):
if not hasattr(self, '_user_class'):
self._user_class = get_model(*settings.CUSTOM_USER_MODEL.split('.', 2))
if not self._user_class:
raise ImproperlyConfigured('Could not get custom user model')
return self._user_class
Файл settings.py
:
AUTHENTICATION_BACKENDS = (
'myproject.auth_backends.CustomUserModelBackend',
'django.contrib.auth.backends.ModelBackend',
)
...
CUSTOM_USER_MODEL = 'accounts.CustomUser'
Вот и всё. Теперь, когда вы обратитесь к request.user
, вы получите экземпляр класса CustomUser
со всеми вашими дополнительными полями и методами.
Интерфейс администратора
А вот так подменяем пользователя в админке:
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.contrib.auth.forms import UserChangeForm
from django.utils.translation import ugettext_lazy as _
from my_project.authuser.models import CustomUser
class CustomUserChangeForm(UserChangeForm):
u"""Обеспечивает правильный функционал для поля с паролем и показ полей профиля."""
password = ReadOnlyPasswordHashField(
label=_("Password"),
help_text=_("Raw passwords are not stored, so there is no way to see "
"this user's password, but you can change the password "
"using <a href=\"password/\">this form</a>."))
def clean_password(self):
return self.initial["password"]
class Meta:
model = CustomUser
class CustomUserAdmin(UserAdmin):
form = CustomUserChangeForm
list_display = ('username', 'last_name', 'first_name',
'is_staff', 'is_active')
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': (
'first_name', 'last_name', 'email', 'timezone'
)}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
(_('Groups'), {'fields': ('groups',)}),
)
admin.site.unregister(User)
admin.site.register(CustomUser, CustomUserAdmin)