通常、ユーザーに関する追加データ、たとえば、短い伝記(約)、生年月日、場所、およびその他の同様のデータを保存する必要があります。
この記事では、ゼロから作成するのではなく、カスタムDjangoモデルを拡張するために使用できる戦略について説明します。
拡張戦略
Djangoユーザーモデルを拡張するための戦略と、アプリケーションの必要性について簡単に説明します。 そして、各戦略の構成の詳細を明らかにします。
単純なモデル拡張(プロキシ)
この戦略では、データベースに新しいテーブルを作成しません。 既存のデータベーススキーマに影響を与えずに、既存のモデルの動作を変更するために使用します(たとえば、デフォルトの順序付け、新しいメソッドの追加など)。
データベースに追加情報を保存する必要はなく、単にメソッドを追加したり、モデルのクエリマネージャーを変更したりする場合に、この戦略を使用できます。 →
ユーザーモデル(ユーザープロファイル)との1対1の通信の使用
これは、データベース内に独自のテーブルを持つオプションの通常のDjangoモデルを使用する戦略であり、標準モデルのユーザーがOneToOneField
リンクを介してリンクします。
この戦略を使用して、認証プロセスに関連しない追加情報(生年月日など)を保存できます。 これは通常、ユーザープロファイルと呼ばれます。 →
AbstractBaseUser
これは、AbstractBaseUser
から継承された完全に新しいユーザーモデルを使用するための戦略です。 特別な注意とsettings.pyの設定の変更が必要です。 データベーススキーマに大きな影響を与えるため、プロジェクトの最初に行うことが理想的です。
サイトに認証プロセスに関する特定の要件がある場合、この戦略を使用できます。 たとえば、場合によっては、ユーザー名の代わりに電子メールアドレスをトークンIDとして使用することが理にかなっています。 →
AbstractUser
これは、AbstractUser
から継承された新しいユーザーモデルを使用するための戦略です。 特別な注意とsettings.pyの設定の変更が必要です。 データベーススキーマに大きな影響を与えるため、プロジェクトの最初に行うことが理想的です。
Django認証プロセス自体が完全に満足のいくものであり、変更したくない場合に、この戦略を使用できます。 ただし、追加のクラスを作成する必要なしに、ユーザーモデルにいくつかの追加情報を直接追加する必要があります(オプション2)。 →
単純なモデル拡張(プロキシ)
これは、ユーザーモデルを拡張する最も時間のかからない方法です。 欠点は完全に限定されていますが、幅広い可能性はありません。
models.py
from django.contrib.auth.models import User from .managers import PersonManager class Person(User): objects = PersonManager() class Meta: proxy = True ordering = ('first_name', ) def do_something(self): ...
上記の例では、
User
モデルの拡張を
Person
モデルで定義しました。 Djangoは、
class Meta
内に次のプロパティを追加することにより、プロキシモデルであると言います。
Proxy = True
また、この例では、カスタムモデルマネージャーが割り当てられ、デフォルトの順序が変更され、新しい
do_something()
メソッドが定義されています。
User.objects.all()
と
Person.objects.all()
が同じデータベーステーブルをクエリすることに注意してください。 唯一の違いは、プロキシモデルに対して定義する動作です。
ユーザーモデル(ユーザープロファイル)との1対1の通信の使用
ほとんどの場合、これが必要です。 個人的には、ほとんどの場合、この方法を使用しています。 ユーザーモデルに関連する追加情報を保存するために、新しいDjangoモデルを作成します。
この戦略を使用すると、追加のクエリまたはクエリ内の接続が生成されることに注意してください。 基本的に、データをリクエストするたびに、追加のリクエストがトリガーされます。 しかし、これはほとんどの場合回避できます。 これを行う方法について、いくつかの言葉を以下に述べます。
models.py
from django.db import models from django.contrib.auth.models import User class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) bio = models.TextField(max_length=500, blank=True) location = models.CharField(max_length=30, blank=True) birth_date = models.DateField(null=True, blank=True)
ここで少し魔法を追加しましょう。ユーザーモデルデータを作成/変更するときに
Profile
モデルが自動的に更新されるように、信号を定義します。
from django.db import models from django.contrib.auth.models import User from django.db.models.signals import post_save from django.dispatch import receiver class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) bio = models.TextField(max_length=500, blank=True) location = models.CharField(max_length=30, blank=True) birth_date = models.DateField(null=True, blank=True) @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): if created: Profile.objects.create(user=instance) @receiver(post_save, sender=User) def save_user_profile(sender, instance, **kwargs): instance.profile.save()
save_user_profile()
create_user_profile()
と
save_user_profile()
を、
User
モデルを保存するイベントにフックしました。 この信号は
post_save
と呼ばれ
post_save
。
そして、
Profile
データを使用したDjangoテンプレートの例:
<h2>{{ user.get_full_name }}</h2> <ul> <li>Username: {{ user.username }}</li> <li>Location: {{ user.profile.location }}</li> <li>Birth Date: {{ user.profile.birth_date }}</li> </ul>
そして、あなたはこれを好きにすることができます:
def update_profile(request, user_id): user = User.objects.get(pk=user_id) user.profile.bio = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit...' user.save()
一般的に、
Profile
永続化メソッドを呼び出さないでください。 これはすべて、
User
モデルを使用して行われ
User
。
フォームを使用する必要がある場合は、このためのサンプルコードを以下に示します。 一度に(1つのフォームから)複数のモデル(クラス)からのデータを処理できることに注意してください。
forms.py
class UserForm(forms.ModelForm): class Meta: model = User fields = ('first_name', 'last_name', 'email') class ProfileForm(forms.ModelForm): class Meta: model = Profile fields = ('url', 'location', 'company')
views.py
@login_required @transaction.atomic def update_profile(request): if request.method == 'POST': user_form = UserForm(request.POST, instance=request.user) profile_form = ProfileForm(request.POST, instance=request.user.profile) if user_form.is_valid() and profile_form.is_valid(): user_form.save() profile_form.save() messages.success(request, _('Your profile was successfully updated!')) return redirect('settings:profile') else: messages.error(request, _('Please correct the error below.')) else: user_form = UserForm(instance=request.user) profile_form = ProfileForm(instance=request.user.profile) return render(request, 'profiles/profile.html', { 'user_form': user_form, 'profile_form': profile_form })
profile.html
<form method="post"> {% csrf_token %} {{ user_form.as_p }} {{ profile_form.as_p }} <button type="submit">Save changes</button> </form>
そして、約束されたクエリの最適化について。 完全に、問題は私の別の記事で検討されています。
しかし、要するに、Djangoの関係は怠zyです。 Djangoは、フィールドの1つを読み取る必要がある場合、データベーステーブルに要求を出します。 この例では、
select_related()
メソッドを使用すると効果的です。
関連付けられたデータにアクセスする必要があることを事前に知っていれば、1つの要求でこれを事前に行うことができます。
users = User.objects.all().select_related('Profile')
拡張機能AbstractBaseUser
正直に言うと、私はこの方法をどうしても避けようとします。 しかし、時にはこれは不可能です。 そしてそれは素晴らしいことです。 より良いまたはより悪い解決策のようなものはほとんどありません。 ほとんどの場合、多かれ少なかれ適切なソリューションがあります。 これが最適なソリューションである場合は、先に進みます。
私は一度それをしなければなりませんでした。 正直なところ、これを行うためのよりクリーンな方法があるかどうかはわかりませんが、他には何も見つかりませんでした。
メールアドレスを
auth token
として使用する必要があり、
username
絶対に必要ありませんでした。 さらに、Django Adminを使用しなかったため、
is_staff
フラグは必要ありませんでした。
これは私が自分のユーザーモデルを定義する方法です。
from __future__ import unicode_literals from django.db import models from django.contrib.auth.models import PermissionsMixin from django.contrib.auth.base_user import AbstractBaseUser from django.utils.translation import ugettext_lazy as _ from .managers import UserManager class User(AbstractBaseUser, PermissionsMixin): email = models.EmailField(_('email address'), unique=True) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=30, blank=True) date_joined = models.DateTimeField(_('date joined'), auto_now_add=True) is_active = models.BooleanField(_('active'), default=True) avatar = models.ImageField(upload_to='avatars/', null=True, blank=True) objects = UserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = [] class Meta: verbose_name = _('user') verbose_name_plural = _('users') def get_full_name(self): ''' Returns the first_name plus the last_name, with a space in between. ''' full_name = '%s %s' % (self.first_name, self.last_name) return full_name.strip() def get_short_name(self): ''' Returns the short name for the user. ''' return self.first_name def email_user(self, subject, message, from_email=None, **kwargs): ''' Sends an email to this User. ''' send_mail(subject, message, from_email, [self.email], **kwargs)
「標準」ユーザーモデルに可能な限り近づけたいと思いました。
AbstractBaseUser
から継承した
AbstractBaseUser
いくつかのルールに従う必要があります。
-
USERNAME_FIELD
モデルフィールドの名前を含む文字列。一意の識別子として使用されます(定義ではunique=True
)。
-
REQUIRED_FIELDS
-createsuperuser
制御createsuperuser
を使用してユーザーを作成するときに要求されるフィールド名のリスト
-
is_active
ユーザーが「アクティブ」と見なされるかどうかを示す論理属性。
-
get_full_name()
-ユーザーの長い説明:必ずしもユーザーのフルネームではなく、ユーザーを説明する任意の文字列を使用できます。
-
get_short_name()
は、ユーザーの簡単な説明です。たとえば、ユーザーの名前やニックネームです。
私は自分の
UserManager
も持っていました。 既存のマネージャーが
create_superuser()
および
create_superuser()
メソッドを定義しているためです。
私のUserManagerは次のようになりました。
from django.contrib.auth.base_user import BaseUserManager class UserManager(BaseUserManager): use_in_migrations = True def _create_user(self, email, password, **extra_fields): """ Creates and saves a User with the given email and password. """ if not email: raise ValueError('The given email must be set') email = self.normalize_email(email) user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_user(self, email, password=None, **extra_fields): extra_fields.setdefault('is_superuser', False) return self._create_user(email, password, **extra_fields) def create_superuser(self, email, password, **extra_fields): extra_fields.setdefault('is_superuser', True) if extra_fields.get('is_superuser') is not True: raise ValueError('Superuser must have is_superuser=True.') return self._create_user(email, password, **extra_fields)
基本的に、
username
と
is_staff
から既存の
UserManager
をクリアしました。
最後の仕上げ。
settings.py
を変更する必要があり
settings.py
:
AUTH_USER_MODEL = 'core.User'
したがって、提供された「in box」の代わりにカスタムモデルを使用するようDjangoに指示します。 上記の例では、
core
というアプリケーション内にカスタムモデルを作成しました。
このモデルを参照する方法は?
2つの方法があります。
Course
というモデルを考えてみましょう:
from django.db import models from testapp.core.models import User class Course(models.Model): slug = models.SlugField(max_length=100) name = models.CharField(max_length=100) tutor = models.ForeignKey(User, on_delete=models.CASCADE)
全体的に大丈夫。 ただし、他のプロジェクトでアプリケーションを使用するか配布する場合は、次のアプローチを使用することをお勧めします。
from django.db import models from django.conf import settings class Course(models.Model): slug = models.SlugField(max_length=100) name = models.CharField(max_length=100) tutor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
拡張機能AbstractUser
django.contrib.auth.models.AbstractUser
クラスは、抽象モデルとして完全なデフォルトのユーザーモデル実装を提供するため、これは非常に簡単です。
from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): bio = models.TextField(max_length=500, blank=True) location = models.CharField(max_length=30, blank=True) birth_date = models.DateField(null=True, blank=True)
その後、
settings.py
を変更する必要があり
settings.py
:
AUTH_USER_MODEL = 'core.User'
前の戦略と同様に、理想的には、データベーススキーマ全体を変更するため、プロジェクトの最初に特別な注意を払ってこれを行う必要があります。 また
from django.conf import settings
設定
from django.conf import settings
、
User
クラスを直接参照する代わりに
from django.conf import settings
を使用して、ユーザーモデルへのキーを作成することも良いルールです。
まとめ
いいね! 「標準」ユーザーモデルの4つの異なる拡張戦略を検討しました。 できる限り詳細にこれを実行しようとしました。 しかし、私が言ったように、これ以上の解決策はありません。 すべてはあなたが受け取りたいものに依存します。
- モデル(プロキシ)の単純な拡張 -認証プロセスとユーザーデータの保存に満足しているので、いくつかの動作を変更したいだけです。
- ユーザーモデル(ユーザープロファイル)と1対1の通信を使用する -認証プロセスには満足していますが、追加のユーザーデータを追加します(データは特別なモデルに保存されます)。
-
AbstractBaseUser
すぐに使用できる認証プロセスは、あなたには適していません。 -
AbstractUser
認証プロセスには満足していますが、追加のユーザーデータを追加したいと思います(データは特別なモデルではなく、ユーザーモデルに直接保存されます)。
オリジナル