Djangoユーザーモデル拡張戦略

Djangoには、優れたユーザー認証システムが組み込まれています。 ほとんどの場合、そのまま使用でき、開発者とテスターの時間を大幅に節約できます。 ただし、サイトのニーズに合わせて拡張する必要がある場合があります。

通常、ユーザーに関する追加データ、たとえば、短い伝記(約)、生年月日、場所、およびその他の同様のデータを保存する必要があります。

この記事では、ゼロから作成するのではなく、カスタムDjangoモデルを拡張するために使用できる戦略について説明します。



拡張戦略


Djangoユーザーモデルを拡張するための戦略と、アプリケーションの必要性について簡単に説明します。 そして、各戦略の構成の詳細を明らかにします。

  1. 単純なモデル拡張(プロキシ)

    この戦略では、データベースに新しいテーブルを作成しません。 既存のデータベーススキーマに影響を与えずに、既存のモデルの動作を変更するために使用します(たとえば、デフォルトの順序付け、新しいメソッドの追加など)。

    データベースに追加情報を保存する必要はなく、単にメソッドを追加したり、モデルのクエリマネージャーを変更したりする場合に、この戦略を使用できます。

  2. ユーザーモデル(ユーザープロファイル)との1対1の通信の使用

    これは、データベース内に独自のテーブルを持つオプションの通常のDjangoモデルを使用する戦略であり、標準モデルのユーザーがOneToOneFieldリンクを介してリンクします。

    この戦略を使用して、認証プロセスに関連しない追加情報(生年月日など)を保存できます。 これは通常、ユーザープロファイルと呼ばれます。

  3. AbstractBaseUser

    これは、 AbstractBaseUserから継承された完全に新しいユーザーモデルを使用するための戦略です。 特別な注意とsettings.pyの設定の変更が必要です。 データベーススキーマに大きな影響を与えるため、プロジェクトの最初に行うことが理想的です。

    サイトに認証プロセスに関する特定の要件がある場合、この戦略を使用できます。 たとえば、場合によっては、ユーザー名の代わりに電子メールアドレスをトークンIDとして使用することが理にかなっています。

  4. 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いくつかのルールに従う必要があります。


私は自分の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) 

基本的に、 usernameis_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 settingsUserクラスを直接参照する代わりにfrom django.conf import settingsを使用して、ユーザーモデルへのキーを作成することも良いルールです。



まとめ


いいね! 「標準」ユーザーモデルの4つの異なる拡張戦略を検討しました。 できる限り詳細にこれを実行しようとしました。 しかし、私が言ったように、これ以上の解決策はありません。 すべてはあなたが受け取りたいものに依存します。


オリジナル
Djangoユーザーモデルを拡張する方法

この投稿に関する質問や意見をお気軽に!

Source: https://habr.com/ru/post/J313764/


All Articles