diadia

興味があることをやってみる。自分のメモを残しておきます。

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 hogehoge.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の使い方についての文章

https://docs.djangoproject.com/ja/2.1/topics/class-based-views/generic-display/#making-friendly-template-contexts

可読性を高めメンテナンスに役に立つ。 

 

get_context_dataメソッドの使い方の文章

例えばdetailViewにAモデルのオブジェクトを表示させ、同時にBモデルのオブジェクトをリスト化する事ができる。
https://docs.djangoproject.com/ja/2.1/topics/class-based-views/generic-display/#adding-extra-context

get_objectの使い方

https://docs.djangoproject.com/ja/2.1/topics/class-based-views/generic-display/#performing-extra-work

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/

https://docs.djangoproject.com/en/2.1/ref/class-based-views/base/#django.views.generic.base.View.http_method_names

これは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