django ユーザ認証の条件分岐
ユーザ認証に関わる条件分岐について
今回ユーザ認証したユーザとAnonymousUserで表示するものやプログラムの論理構造を変えようと思った。当初書いていたコードは例えば以下のもので、それは期待した挙動を得られなかった。
元のコード
hoge.views.py... from django.shortcut import render from hoge.models import Diary from django.views.generic import Views class DiaryView(View): def get(self, request, *args, **kwargs): if request.user: object_list = Diary.objects.all() else: object_list = Diary.objects.filter(private=False) context = {} context["object_list"] = object_list return render(request, "diary/ondex.html", context)
ユーザ認証している場合にはすべての記事を表示することができるが、認証されていないものは公開しても良いものだけ表示される仕組みを構築したかった。このコードは文法的なエラーが出なかった。しかしAnonymousUserの場合(elseの場合)の表示ができなかった。期待通りの挙動を実現するためにはis_authenticatedを使えば良いとわかった。
改善されたコード
hoge.views.py... from django.shortcut import render from hoge.models import Diary from django.views.generic import Views class DiaryView(View): def get(self, request, *args, **kwargs): if request.user.is_authenticated : #ここを変更された object_list = Diary.objects.all() else: object_list = Diary.objects.filter(private=False) context = {} context["object_list"] = object_list return render(request, "diary/ondex.html", context)
ドキュメント
request.user: https://docs.djangoproject.com/ja/2.1/ref/request-response/#django.http.HttpRequest.user
From the AuthenticationMiddleware: An instance of AUTH_USER_MODEL representing the currently logged-in user. If the user isn't currently logged in, user will be set to an instance of AnonymousUser. You can tell them apart with is_authenticated, like so:
if request.user.is_authenticated: ... # Do something for logged-in users. else: ... # Do something for anonymous users.
request.userは直近でログインしている場合はログインしたuserが返され、ログインしてなければAnonymousUserが返されるようだ。
IntegrityErrorが発生してしまう。。。
エラー対処中 解決次第更新する
django.db.utils.IntegrityError: NOT NULL constraint failed: appname_model.anoteher_model_id
同じエラーが出ている人の記事:
http://nihaoshijie.hatenadiary.jp/entry/2014/06/12/090008
ドキュメントのフィールドオプション、ユニークについて
https://docs.djangoproject.com/ja/2.1/ref/models/fields/#unique
True の場合、そのフィールドはテーブル上で一意となる制約を受けます。 This is enforced at the database level and by model validation. If you try to save a model with a duplicate value in a unique field, a django.db.IntegrityError will be raised by the model's save() method. This option is valid on all field types except ManyToManyField and OneToOneField. Note that when unique is True, you don't need to specify db_index, because unique implies the creation of an index.
uniqueフィールドにダブった値を入れてsave()しようとすると、django.db.IntegrityErrorが生じる。と書いてある。
エラー対処できた。自分の原因はやはりuniqueオプションに関係していた。あるモデルのuniqueフィールドに特定のデータを入れたインスタンスがすでに存在しているにもかかわらず、uniqueフィールドに同じデータを別のインスタンスとして作成や保存しようとするコードになっていた。それが原因だった。
If you see valid patterns in the file then the issue is probably caused by a circular import.
エラー内容
The included URLconf '<module 'apps.urls' from 'hoge/apps/urls.py'>' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.
文章の意味は、apps/urls.pyにurlのパターンが存在しないからエラーが出ている。ちゃんとurlのパターンが書かれている場合にはcircular importが原因の可能性がある。
エラーのあったコード
urlspattern = [ path( 'confirm/', TemplateView.as_view(template_name="apps/confirm.html"), name="confirm" ),]
今回の対処
今回はurls.pyのurlパターンを書けていなかったからエラーになってしまった。urlspatternではなくてurlpatternsが正しい書き方だった。
urlpatterns = [ path( 'confirm/', TemplateView.as_view(template_name="apps/confirm.html"), name="confirm" ),]
過去のエラーの場合
django-admin startproject hoge のhoge.urlsにおいてpath以下の書き方が間違ったときも同じエラーが出てしまった。通常、include()を使う場合は以下のように書く。
from django.urls import path, include urlpatterns = [ path("index", include("app.urls")), ]
しかしながら以下のように書いてしまい、同じエラーを発生させてしまった。
from django.urls import path, include urlpatterns = [ path("index", include("app")), ]
apps.urlsまで書くのが正しい書き方です。
Exception Type : MultipleObjectsReturnedが出るとき
エラー内容
djangoを使っていて以下のエラーが出た。このエラーに対してなんのことか分からなかったのでメモしておく。
Exception Type: MultipleObjectsReturned Exception Value: get() returned more than one Post -- it returned 4!
エラー箇所のコード
post_obj = Post.objects.get(user=request.user)
エラー対処
参考情報:https://stackoverflow.com/questions/32172934/how-to-catch-the-multipleobjectsreturned-error-in-django
djangoのget()は、データオブジェクトをひとつだけ取得する。データオブジェクトが該当しない場合はエラーが出る。またデータオブジェクトが一つではなく複数の場合もエラーが出る。
https://docs.djangoproject.com/ja/2.1/topics/db/queries/#retrieving-a-single-object-with-get
以下のように修正した。
post_obj = Post.objects.filter(user=request.user).order_by("-id").first()
django 日記の作り方
日記アプリを作る
日記タイトル、日付、日記の内容を表示するアプリケーションの作り方。
これはdjango初学者にとってイメージしづらいdjangoの使い方をイメージしてもらいたくて書きました。
環境構築
windows,macを使う場合いずれにしても、anacondaをインストールしてから作ることをおすすめする。
conda create -n django36 python==36
django36は環境名です。この環境にdjangoやpython3.6を入れることになります。
diaryディレクトリを作成する
mkdir diary
cd diary
conda activate django36 #windowsの場合 source activate django36 #macの場合
djangoを環境に入れる
pip install django
pip freeze #djangoをインストールできていればコマンド入力の結果にdjangoと表示されます
djangoでアプリケーション作成を始める
以下のコマンドを打つと、diaryディレクトリにディレクトリ、ファイルが自動的に作成されます。
django-admin startproject diary .
投稿した日記を表示する仕組み(contentsアプリ)を作成する
python manage.py startapp contents
必要な編集
models.py,urls.pyとviews.pyを編集していく。models.pyでは表示したい内容の型を定め、urls.pyではURL入力すると表示を担当するviews.pyにつなげる設定を行う。views.pyでは表示を今回は担当する。
1.diary/urls.pyを編集
diary/urls.py(編集前)... from django.contrib import admin from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), ]
python manage.py startproject diary .と入力すると以上の内容のurls.pyが自動生成される。このファイルにはpath("", include(contents.urls)), を挿入する。
diary/urls.py(編集後)... from django.contrib import admin from django.urls import path, include # includeも追加する import contents.urls urlpatterns = [ path('admin/', admin.site.urls), path('contents/', include(contents.urls)), ]
2.contents/urls.pyを編集
contentsにurls.pyが予め作ってあるわけではないので、urls.pyを作成する。例えばターミナルでurls.pyを作成する場合以下のコマンドを入力する。
cd contents
touch urls.py
contents/urls.py... from django.urls import path from contents.views import lists, detail app_name = "contents" urlpatterns = [ path('', lists, name="lists"), path('<int:pk>', detail, name="detail"), ]
3.contents/models.pyを編集
以下に定める内容がウェブで表示される内容となる。表示したいものを増やしたければ追加すれば良い。
contents/models.py... from django.db import models class Content(models.Model): title = models.CharField(max_length=30) text = models.TextField() date = models.DateTimeField(auto_now=True) def __str__(self): return self.title
4.contents/views.pyを編集
編集前は以下のようになっている。
contents/views.py(編集前)... from django.shortcuts import render # Create your views here.
以下のように編集すれば良い。
contents/views.py(編集後)... from django.shortcuts import render from django.http import HttpResponse from contents.models import Content def lists(request): return HttpResponse("this is test, これはテストです") def detail(request, pk): detail_object = Content.objects.get(id=pk) context = {} context["object"] = detail_object #辞書型データの扱い方法を調べてみてください return render(request, "contents/detail.html", context)
5.マイグレーションする
以下のコマンドでマイグレーションする。マイグレーションするときには、settings.pyのINSTALLED_APPSのリストにアプリを前もって追加しなければならない。
今回の場合はcontentsがアプリなので以下のように追加できれば良い。
5-1.INSTALLED_APPSにアプリを追加
diary/settings.py(編集後)... INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'contents', # これを追加する ]
5-2.マイグレーションの実行
5-2-1.マイグレーションのコマンドを実行
python manage.py makemigrations
このコマンドの結果がこんな感じなら大丈夫です。
(コマンドの結果) Migrations for 'contents': contents/migrations/0001_initial.py - Create model Content
5-2-2.migrateする
python manage.py migrate
このコマンドの結果がこんな感じなら大丈夫です。
Operations to perform: Apply all migrations: admin, auth, contents, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying contents.0001_initial... OK Applying sessions.0001_initial... OK
内容を登録する
内容とは今回の場合は日記です。title, text, dateを入力することが内容を登録することです。
内容の登録方法は管理者ページ(admin)から行う方法、内容登録画面を作成して内容登録する方法があります。他にもShellからの方法、APIからの方法といろいろありますが簡単に構築できる方法は上記の2つです。今回は管理者ページから内容を登録します。
管理者ページとは
runserverした状態で、ブラウザから"localhost:8000/admin"と入力すると表示されるページを管理者ページと言います。これは今作成しているウェブアプリケーションを運営管理する機能のものです。ここで運営管理するユーザーの設定を変えたり、今回で言えば登録した日記の内容を変えたり、タイトルを変えたりするときに使います。今回はここから日記の内容を作成する。
管理者ページには管理者権限があるユーザが必要
localhost:8000/adminとするとログイン画面が表示される。管理者権限を持つユーザでしかログインできない。
スーパーユーザーの作成する
python manage.py createsuperuser
入力するとusernameの入力があります。適当にusernameを決めて入力してください。
その次にE-mail addressを入力を要求されます。ここは入力しても入力しなくても大丈夫です。
次にpasswordの入力です。passwordは一度入力し、確認のために更に入力を求められます。passwordの入力じには自分がうったpasswordが画面には表示されませんが、心配しなくても入力できていますので安心してください。。
(コマンドの結果) Superuser created successfully.
このように表示されればスーパーユーザが作成されました。このスーパーユーザを使ってadminページにアクセスします。
内容を登録する
Contentsを選んで内容を登録します。2,3個作ってみてください。これで準備が整いました。
ページが表示されるか確認してみる
コンソール画面でpython manege.py runserver を行った上で、試しに'localhost:8000/contents'そして'localhost:8000/contents/1','localhost:8000/contents/2'とブラウザに打ち込んでみてください。
localhost:8000/contentsと入力した場合には、
this is test, これはテストです
と表示されるはずです。一方、'localhost:8000/contents/1','localhost:8000/contents/2'と入力した場合には以下のようなエラーが書かれたページが表示されます。
TemplateDoesNotExist at /contents/1
views.pyで使ったrenderはテンプレートを必要としているのにもかかわらず、テンプレートを準備していないことに起因するエラーです。render()はテンプレートが必要なものだと覚えておいてください。
テンプレートを作成する
contents/templates/contents以下にhome.html, detail.htmlを作成します。このhome.html, detail.htmlをテンプレートと呼びます。
cd contents # contentsディレクトリに移動 mkdir templates # templatesディレクトリを作成 cd templates # templatesディレクトリに移動 touch home.html # home.html を作成 touch detail.html # detail.html を作成
detail.htmlのテンプレートを作成する
上記の処理で作成したdetail.htmlに編集を加えていきます。表示するためだけの必要最低限のことだけ編集します。なおこの段階でテンプレート自体は存在するので、'localhost:8000/contents/1'または'localhost:8000/contents/2'とアクセスしてもエラーは出なくなります。
<h1>{{ object.title }}</h1> <br /> {{ object.text }}
再びアクセスしてみる
'localhost:8000/contents/1'または'localhost:8000/contents/2'でアクセスしてみてください。日記のタイトルと内容が表示されたと思います。
emailとpasswordで認証する仕組みにする
他の方法も
下記の記事はUserモデルを独自のモデルにして識別子(identifier)をemailにすることで認証をemailで行うように実装する方法だ。 他方で認証バックエンドにカスタムバックエンドを準備し、それを使うことでEmail認証する方法も成功したのでそのうち記事を書きたい。とりあえずリポジトリだけ上げておく。
authentication_backends_sample
ドキュメント:https://docs.djangoproject.com/ja/2.1/topics/auth/customizing/
EmailとPasswordで認証するために
方針はdjangoのUserモデルを書き換えemailとpasswordで認証する。
Userモデルは以下から構成される。
- AbstractUser
- AbstractBaseUser
- PermissionsMixin
これらのモデルを継承し、自分に都合の良いモデルを作成するわけだけど、なぜ継承するのか? それはdjangoの特徴に関係している。djangoの特徴はユーザー認証が予め提供されていることである。djangoのユーザ認証をの仕組みを利用しつつ開発すれば、認証技術については早く、精度の高いものが出来上がる。だから継承という手続きをとる。
また認証ではusernameは不要なので存在すれば消去する。
AbstractBaseUserはどうなっているか
属性だけ見れば以下のようになっている。
class AbstractBaseUser(models.Model): password = models.CharField(_('password'), max_length=128) last_login = models.DateTimeField(_('last login'), blank=True, null=True) is_active = True
emailフィールドがないのでemailフィールドを付け加えれば、emailとpasswordで完成しそうだ。ただし、PermissionsMixinを統合させる前のモデルなのでPermissionsMixin の機能がないことがデメリットだ。これを自分で補うことになりそうだ。
AbstractUserはどうなっているか
属性だけ見れば以下のようになっている。
class AbstractUser(AbstractBaseUser, PermissionsMixin): """ An abstract base class implementing a fully featured User model with admin-compliant permissions. Username and password are required. Other fields are optional. """ username_validator = UnicodeUsernameValidator() username = models.CharField( _('username'), max_length=150, unique=True, help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'), validators=[username_validator], error_messages={ 'unique': _("A user with that username already exists."), }, ) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=150, blank=True) email = models.EmailField(_('email address'), blank=True) is_staff = models.BooleanField( _('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.'), ) is_active = models.BooleanField( _('active'), default=True, help_text=_( 'Designates whether this user should be treated as active. ' 'Unselect this instead of deleting accounts.' ), ) date_joined = models.DateTimeField(_('date joined'), default=timezone.now) objects = UserManager() EMAIL_FIELD = 'email' USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email']
emailのフィールドがすでに存在している。これではユーザ認証に役立たないので上書き修正する必要があるようだ。emailにはユニークオプションをつけたい。usernameが存在しているので、ここを修正したい。またUSERNAME_FIELD = 'username'となっているのでusernameがログイン時に必要となってしまう。これをUSERNAME_FIELD = 'email'に書き換える事が必要。
どちらのモデルを利用するか
PermissionsMixin関係が複雑そうなので、すでに統合されているAbstractUserを書き換えてみる。
class Meta について
一体何なのか
Metaを使う場面
よく分からずMetaを使っていた。というかコードをコピペしてた。これはなんなのか? 自分が使う場面は、form関係ではModelFormの継承時に使う。こんな感じで。
from django import forms class MessageForm(forms.ModelForm): class Meta: model = Message fields = ["your_name","your_email","message"]
ドキュメント:https://docs.djangoproject.com/ja/2.1/ref/models/options/
少しわかった
抽象基底クラスは、複数の他モデルに対して共通の情報を入れ込みたいときに有用です。基底クラスを書いて Meta クラス内で abstract=True をセットしてください。これで、このモデルはデータベーステーブルを作成するために使用されることはなくなります。 代わりに、他のモデルで基底クラスとして使われる際に、これら子クラスのフィールドとして追加されます。
何が言いたいのか?
これはおそらく以下のようなものだろう。まずMetaを自作したクラス内で記述する場合には、そのクラス自身をテーブルとして利用する意図がない。テーブルとして利用せず、このクラスを継承した子クラスにフィールドとして利用することを考えている。そのためMetaを書くことで、python manage.py makemigrations, python manage.py migrateの影響を受けなくできる。影響を受けていては使わないテーブルが量産されて邪魔になってしまう。こんなところか。また分かり次第この記事を書き換える。
Metaを使っている例
例えば、django提供のユーザーモデルにはMetaが使われている。
django.contrib.auth.models.UserはAbstractUserとAbstractBaseUserとPermissionsMixinで構成されている。makemigrationsをしても、Metaの記述がなければ、AdminにはAbstractUserとAbstractBaseUserとPermissionsMixinが表示されてしまうことになるだろう。それはそれはとてもうざいことでしょう。 Userモデル関連のソースコード:http://torajirousan.hatenadiary.jp/entry/2019/02/06/144704
ビルトインUserモデルを修正変更したい場合
ベース記事_Userモデル
内容一覧
ビルトインのユーザーモデルの呼び出し方
from django.contrib.auth.models import User
ビルトインのUserモデルを修正変更したい場合
既存のUserモデルを変更したい場面に出くわした。自分の場合はmodels.pyにあるフィールドにUserモデルを外部キーとして利用する。その際にon_delete.models.SET_NULLと設定したが、エラーが出てしまう。これはUserモデルのuserフィールドでnull=Trueと設定されていることが条件である。しかしながらUserモデルをnull=Trueに修正変更することは無理?っぽい。 nullをセットしたい要望がある場合には、カスタムユーザーモデルを準備しなければならないことがわかった。 カスタムユーザーモデルとは、djangoで提供しているUserモデルやその関連するソースコードをもとに継承したモデルのことである。
実際にUserモデルのソースコードを確認する
探し方について
djangoのビルトインUSERモデルのソースコードは以下にある。
https://github.com/django/django/blob/master/django/contrib/auth/models.py
Userモデルは、from django.contrib.auth.models import Userと呼び出すので、githubでは、django,contrib,auth,modelsの順番でディレクトリ、ファイル移動すればUserモデルのソースコードを発見できる。
Userは、AbstractUserを継承したものである。またAbstractUserはAbstractBaseUserとPermissionsMixinを複合で継承したものである。Userを理解するためにはこれらのものがどのように記述されているか確認することが必要である。以下にコードを置く。
User
https://github.com/django/django/blob/master/django/contrib/auth/models.py
class User(AbstractUser): """ Users within the Django authentication system are represented by this model. Username and password are required. Other fields are optional. """ class Meta(AbstractUser.Meta): swappable = 'AUTH_USER_MODEL'
AbstractUser
https://github.com/django/django/blob/master/django/contrib/auth/models.py
class AbstractUser(AbstractBaseUser, PermissionsMixin): """ An abstract base class implementing a fully featured User model with admin-compliant permissions. Username and password are required. Other fields are optional. """ username_validator = UnicodeUsernameValidator() username = models.CharField( _('username'), max_length=150, unique=True, help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'), validators=[username_validator], error_messages={ 'unique': _("A user with that username already exists."), }, ) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=150, blank=True) email = models.EmailField(_('email address'), blank=True) is_staff = models.BooleanField( _('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.'), ) is_active = models.BooleanField( _('active'), default=True, help_text=_( 'Designates whether this user should be treated as active. ' 'Unselect this instead of deleting accounts.' ), ) date_joined = models.DateTimeField(_('date joined'), default=timezone.now) objects = UserManager() EMAIL_FIELD = 'email' USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email'] class Meta: verbose_name = _('user') verbose_name_plural = _('users') abstract = True def clean(self): super().clean() self.email = self.__class__.objects.normalize_email(self.email) def get_full_name(self): """ Return 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): """Return the short name for the user.""" return self.first_name def email_user(self, subject, message, from_email=None, **kwargs): """Send an email to this user.""" send_mail(subject, message, from_email, [self.email], **kwargs)
PermissionsMixin
https://github.com/django/django/blob/master/django/contrib/auth/models.py
class PermissionsMixin(models.Model): """ Add the fields and methods necessary to support the Group and Permission models using the ModelBackend. """ is_superuser = models.BooleanField( _('superuser status'), default=False, help_text=_( 'Designates that this user has all permissions without ' 'explicitly assigning them.' ), ) groups = models.ManyToManyField( Group, verbose_name=_('groups'), blank=True, help_text=_( 'The groups this user belongs to. A user will get all permissions ' 'granted to each of their groups.' ), related_name="user_set", related_query_name="user", ) user_permissions = models.ManyToManyField( Permission, verbose_name=_('user permissions'), blank=True, help_text=_('Specific permissions for this user.'), related_name="user_set", related_query_name="user", ) class Meta: abstract = True def get_group_permissions(self, obj=None): """ Return a list of permission strings that this user has through their groups. Query all available auth backends. If an object is passed in, return only permissions matching this object. """ permissions = set() for backend in auth.get_backends(): if hasattr(backend, "get_group_permissions"): permissions.update(backend.get_group_permissions(self, obj)) return permissions def get_all_permissions(self, obj=None): return _user_get_all_permissions(self, obj) def has_perm(self, perm, obj=None): """ Return True if the user has the specified permission. Query all available auth backends, but return immediately if any backend returns True. Thus, a user who has permission from a single auth backend is assumed to have permission in general. If an object is provided, check permissions for that object. """ # Active superusers have all permissions. if self.is_active and self.is_superuser: return True # Otherwise we need to check the backends. return _user_has_perm(self, perm, obj) def has_perms(self, perm_list, obj=None): """ Return True if the user has each of the specified permissions. If object is passed, check if the user has all required perms for it. """ return all(self.has_perm(perm, obj) for perm in perm_list) def has_module_perms(self, app_label): """ Return True if the user has any permissions in the given app label. Use similar logic as has_perm(), above. """ # Active superusers have all permissions. if self.is_active and self.is_superuser: return True return _user_has_module_perms(self, app_label)
AbstractBaseUser
https://github.com/django/django/blob/master/django/contrib/auth/base_user.py
class AbstractBaseUser(models.Model): password = models.CharField(_('password'), max_length=128) last_login = models.DateTimeField(_('last login'), blank=True, null=True) is_active = True REQUIRED_FIELDS = [] # Stores the raw password if set_password() is called so that it can # be passed to password_changed() after the model is saved. _password = None class Meta: abstract = True def __str__(self): return self.get_username() def save(self, *args, **kwargs): super().save(*args, **kwargs) if self._password is not None: password_validation.password_changed(self._password, self) self._password = None def get_username(self): """Return the username for this User.""" return getattr(self, self.USERNAME_FIELD) def clean(self): setattr(self, self.USERNAME_FIELD, self.normalize_username(self.get_username())) def natural_key(self): return (self.get_username(),) @property def is_anonymous(self): """ Always return False. This is a way of comparing User objects to anonymous users. """ return False @property def is_authenticated(self): """ Always return True. This is a way to tell if the user has been authenticated in templates. """ return True def set_password(self, raw_password): self.password = make_password(raw_password) self._password = raw_password def check_password(self, raw_password): """ Return a boolean of whether the raw_password was correct. Handles hashing formats behind the scenes. """ def setter(raw_password): self.set_password(raw_password) # Password hash upgrades shouldn't be considered password changes. self._password = None self.save(update_fields=["password"]) return check_password(raw_password, self.password, setter) def set_unusable_password(self): # Set a value that will never be a valid hash self.password = make_password(None) def has_usable_password(self): """ Return False if set_unusable_password() has been called for this user. """ return is_password_usable(self.password) def get_session_auth_hash(self): """ Return an HMAC of the password field. """ key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash" return salted_hmac(key_salt, self.password).hexdigest() @classmethod def get_email_field_name(cls): try: return cls.EMAIL_FIELD except AttributeError: return 'email' @classmethod def normalize_username(cls, username): return unicodedata.normalize('NFKC', username) if isinstance(username, str) else username
class based view メモ
基本的にここに書いてある内容で自分でわかったことをメモしていく。
https://docs.djangoproject.com/ja/2.1/topics/class-based-views/
context_object_nameの使い方についての文章
可読性を高めメンテナンスに役に立つ。
get_context_dataメソッドの使い方の文章
例えばdetailViewにAモデルのオブジェクトを表示させ、同時にBモデルのオブジェクトをリスト化する事ができる。
https://docs.djangoproject.com/ja/2.1/topics/class-based-views/generic-display/#adding-extra-context
get_objectの使い方
class based view メモ
例えばDetailviewを継承したクラスにクラスメソッドとしてdef post(self, request, *args, **kwargs):として書くと、request.POSTが動く。でもdef sss(self, request, *args, **kwargs):と書いて同じ内容のコードを記述してもエラーになってしまう。 この現象について分かっていない。
これについて
https://ccbv.co.uk/projects/Django/2.1/django.views.generic.detail/DetailView/
これはhttp_method_namesに含まれるものがある場合にはレスポンスを返すように動く。
推測
class Viewをもとに考える。まず参考資料。 classmethod as_view(**initkwargs)の役割は、リクエストを受け取ってレスポンスを返す、呼び出し可能なビューを返します、とある。 つまりView.as_view()はリクエストを受け取り、レスポンスを返す。
レスポンス = View.as_view()
https://udomomo.hatenablog.com/entry/2018/08/17/010011 これを見ればよかった
LoginViewを使ったログイン実装
以前までのログイン実装方法
以前までviews.pyにはlogin用の関数を定義してログインする方法をとっていた。
ユーザーログイン機能をdjangoプロジェクトに実装する
LoginViewによるログイン機能の実装
LoginViewを使うことでurls.py,settings.pyとテンプレート(html)を作成するだけで完成する。慣れていれば2分もかからずとも実装できるのが魅力。
実装例
accounts/urls.pyにて #accountsはアプリ名 from django.urls import path from django.contrib.auth.views import LoginView app_name = "accounts" urlpatterns = [ path('login/', LoginView.as_view(), name="login"), ]
project名/settings.pyにて LOGIN_REDIRECT_URL = 'products:product_list' # LOGIN_REDIRECT_URL ='アプリ名':'urls.pyのname' を挿入
templates/registration/login.htmlにて <form method="POST"> {% csrf_token %} {{ form }} <button type="submit">ログイン</button>
参考情報
LoginViewのアトリビュート情報:class LoginView
LoginViewのアトリビュートのひとつにform_classがある。それはデフォルトで以下のように定められている。
form_class = <class 'django.contrib.auth.forms.AuthenticationForm'>
通常Updateviewのようなフォームを伴うViewにはfieldsやform_classを定める必要があるが、LoginViewの場合にはすでに定められている。
LOGIN_REDIRECT_URLについて
https://docs.djangoproject.com/ja/2.1/topics/auth/default/#django.contrib.auth.views.LoginView
https://docs.djangoproject.com/ja/2.1/ref/settings/#std:setting-LOGIN_REDIRECT_URL
Loginフォームに正しく入力してログインすると、デフォルトで'/accounts/profile/'のurlにリダイレクトする仕様にLoginViewはできている。これをurls.pyで定めるか、settings.pyにてリダイレクトurlを定義してあげればエラーが解消される。
formについてメモ
formの整理
- html上でformタグで書く方法
- forms.Formを使う方法
html上でformタグで書く方法
概要
html上でformタグでフォームを作成する。このときnameを忘れない。views.pyにて、入力されたデータはrequest.postに格納される。request.postは辞書型に似たものなので["name"]というキーを使うと取得できる(request.post["name"])。で、取得したデータを自作でバリデーションする。
forms.Formを使う方法
概要
例えばforms.pyでforms.Formを継承したクラスを作成する。そしてクラスインスタンスをviews.pyで作成し、当該インスタンスをrender()関数に渡したり、HttpResponse()関数に渡す。で、アクセスするとフォーム欄が表示されたことを確認できる。
forms.Formで継承したformクラスの変数は、html上はnameとして扱われる。htmlで入力された情報は、この変数を介してviews.py内の論理に組み込まれる。
CreateViewを使う方法
概要
CreateViewうぃ継承したクラスのプロパティは2つ定めれば使うことができる。
テンプレートには{{ forms }}を使うことが指定されている。またリクエストPOSTのためのbuttonタグは自分でテンプレートに記述する必要がある。
{{ forms }} のデザインを変更することはできるのか?ココが今の課題
どうやらサードパーティのライブラリを使うとできるようだ。どうしてもと言うなら CreateViewを使わずにViewを使ってforms.Formを使う方法も考えられる。
https://docs.djangoproject.com/ja/2.2/ref/forms/api/#checking-which-form-data-has-changed
Mailchimp api用のlistIDの場所
apiで使うlistIDのありか
分かりにくい!ようやく見つかった。
https://admin.mailchimp.com/lists/
こちらにアクセスして、ページ一番下にあるGo to list settingsをクリックする。 するとList IDが書かれているところがある。
参考:https://renzojohnson.com/contributions/contact-form-7-mailchimp-extension/mailchimp-list-id