diadia

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

ドメイン設定について

全然やり方を知らない。

ドメインとipの関係はipをドメインに変換するサーバーがあり、ドメインを入力するとipが戻ってくるのでアクセスできるみたいなことは知っている。

ただ自分が設定するとなるとよくわからなくなる。

 

まずドメイン取得したサービス会社、さくらVPS、または両方から設定をするのか?またはネームサーバーに直接紐付けることを申請するのか?

 

普通に考えればネームサーバーにヒモ付お願いしますと言えば、良いようなものだけども実際はどうなっているのか。。。

 

 

さくらVPSとお名前.comの場合

ドメインを取得し、さくらVPSに設定してみる - Qiita

CSVファイルを受け取る機能(アップロード機能)について

csv関連の資料

https://docs.djangoproject.com/ja/2.1/topics/http/file-uploads/ https://qiita.com/otera05/items/25d5bad759d39d61bc40

 

html上でどのような記述が必要か(試行錯誤過程)

文字列においてはformタグ内にinputやbuttonを設置することで、サーバーサイドで当該文字列を受け取ることが出来た。音声ファイルや映像ファイルそしてcsvファイルに対応するformやinput等は何なのか?ここが分かっていない。
調べたところinputタグの属性としてtype="file"なるものがあるらしい。これを試してみようと思う。

html上でどのような記述が必要か(結論)

こちらの情報通りでうまくいった。
http://www.htmq.com/html5/input_type_file.shtml
ファイルのやり取りにおいてformタグではenctype="multipart/form-data"が必要。またinputタグではtype="file"が必要。これが知らなかった情報だった。

file受取り時の挙動をコントロールしたい

文字列の情報はrequest.POSTに当該情報が辞書型のようなデータとして格納された。だから辞書型データのように取り出せば文字列を扱うことが出来た。csvファイルのようなデータはどのように扱えばよいのか?ここが知らない。csvモジュールを使えばよいのか?またcsvファイルを受け取ったら、そのデータをもとにクラスインスタンスを作成したい。おそらくcsvファイルの受け取りとその中身の読み取りさえできれば、問題は解決するだろう。

file受取り時の挙動をコントロールする

こちらの情報が参考になった。
https://docs.djangoproject.com/ja/2.1/topics/http/file-uploads/#basic-file-uploads
https://docs.djangoproject.com/ja/2.1/ref/request-response/#django.http.HttpRequest.FILES
以下のような感じでコードを書いてみた。

hoge/views.py ...

class ReceiveFileView(View):


    def get(self, request, *args, **kwargs):
		return render(request, 'hoge/receiving_csv.html')

    def post(self, request, *args, **kwargs):
		print(request.FILES)
		print(dir(request.FILES))
		return render(request, 'hoge/receiving_csv.html')
hoge/receiving_csv.html ...

<form action="{% url 'hoge:receive_file' %}" enctype="multipart/form-data" method="POST">
{% csrf_token %}
<input type="file" />     # name="csv"をinputタグの属性として入れること
<button type="submit">ファイルを送信する</button>
</form>

inputタグにname属性を入れなかったせいでrequest.FILESにファイルが格納されず、時間を費やしてしまった。辞書型データとして格納するためにkeyの代わりにnameが必要だとわかった。また、辞書型データとして格納するためには他にも条件がドキュメントに書いてある。ファイルはリクエストポストで送信すること。その他にformタグにenctype="multipart/form-data"を記述していることだった。

今作りたいアプリケーションについて

今作りたいもの

djangoECサイトはそれらしいものが出来てきた。JAVAではECサイト作成は登竜門である、とどこかで聞いたのでやっとそれなりにdjango使い見習いとして言える身分になったと思う。
pythondjangoを通してよく学べたと思う。クラスの継承方法だったり、インスタンスとは何なのかとか知る必要が出てきて結果的にpythonが少し分かるようになってきた。まだまだwebアプリケーションの作成能力がしょぼいのでもっといろいろなものづくりにチャレンジしたい。他にも機械学習バイオインフォマティクスみたいな分野もあるみたいで結構興味がある。まあでもwebアプリケーション作りを続ける。

現状ではクライアントから情報を入力する観点で考えると、formタグからinputフォームを使うような方法でしかシステムを構築できない。音声ファイルや映像ファイルの受け取りや、CSVファイルの受け取りができる機能を持ったアプリケーションが作ってみたい。まずは一番簡単そうなCSVファイルを受け取るものを作ってみたい。

それが出来たらdjangorest APIなるものを載せたものができるらしいのでそれを挑戦してみたい。

検索(サーチ)機能を実装する

検索機能を実装したい

データベースからブログの記事内容だったり、商品を検索して結果を表示したい。

それは検索キーワードに合致する結果を表示すれば良い。 キーワードに合致するものだけ抽出する方法は、objects.filter()を使えば解決できる。 結果を表示するにはListViewを使うかViewを使う。もちろんview関数で定めてあげても良い。
ListViewを使った例はこちら。ポイントはクラスインスタンスメソッドを別途定めてあげることだ。

http://torajirousan.hatenadiary.jp/entry/2018/09/25/025035

 

Viewを使った方法(検索フォームの実装含む。)

流れとしては、テンプレートに検索用のフォームタグを設置する。このフォームタグから入力されたキーワードをfilter()メソッドを使って対象のデータオブジェクトに限定し、テンプレートにレンダリングして表示する。

キーワード入力テンプレートの作成

"products/search.html"...

<form action="{% url 'products:product_search' %}" method="GET">
<input name="q" type="search" placeholder="Search Product by keyword" />
<button type="submit">Search</button>
</form>

半年前はmethodGETの存在を知らなかった。検索用のフォームを作るときはmethod="GET"として、検索キーワードを取得する足がかりを作る。action={***}の役割は以下のものです。フォームになんらかのキーワードが入力される。Searchボタンが押されたときに、views.pyの特定のviewが発動します。
{% url "hoge:foo" %}の中身の部分はviews.pyに記述されているviewのうち、どのviewを発動させるかを指定しています。この場合はhogeアプリケーションおviews.pyのfooが発動します。このview名はurls.pyで名前をつけてあげることが必要になります。

views.pyの作成

"products/views.py"...

from django.shortcut import render
from django.views.generic import View


class ProductSearchListView(View):

    def get(self, request, *args, **kwargs():
        
        q = request.GET["q"]
        print(q)             # ちゃんと入力した文字がqに返されているかコンソールで確認する
      object_list = Product.objects.filter(title__icontains=q)
      context = {}
      context["object_list"] = object_list
      return render(request, "products/product_list.html", context)
    

自分が苦労したひとつにdjangoのrequest.POSTやrequest.GETにまず何らかの情報が格納されているという事実を知らなかった。その事実を知ってからもどんな種類の情報が格納されているか分からなかった。調べる術を持たなかった。どんな情報があるか調べるためにrequest.GET,POSTをいじってみるしかない。
具体的には以下の方法が役に立つ。

class ProductSearchListView(view):
    def get(self, request, *args, **kwargs):
        print(request.GET)
        print("=======================")
        print(dir(request.GET))
        print("=======================")
        print(self.request.GET)

dir()はrequest.GET,POSTに含まれている属性?メソッド?が何であるか表示してくれるので、dir()を足がかりに表示された属性?メソッド?を新たに使ってみてどんな情報があるか探索する方法で自分は取り組んできた。

urls.pyの作成

"products/urls.py"...
from django.urls import path

app_name= "products"
urlpatterns = [
    path('search', ProductSearchListView.as_view(), name="product_search"),
        ]

検索結果を表示するhtmlを作成

"product_list.html"...

{% for object in object_list %}

{{ object.title }}}
{{ object.description }}

{% endfor %}

OR検索を使いたい

Qオブジェクトを使う

and検索はfilter()メソッドで連結させて書くことで挙動を実現できる。一方or検索ではQオブジェクトを使うことで実現できることがわかった。

ドキュメント: https://docs.djangoproject.com/ja/2.1/topics/db/queries/#complex-lookups-with-q-objects
https://docs.djangoproject.com/ja/2.1/ref/models/querysets/#q-objects

Qのインスタンスを作成して運用する方法

参考url:
https://code.i-harness.com/ja-jp/q/d01be

TypeError: unhashable type: 'set' エラー対処

エラー内容

TypeError: unhashable type: 'set'

上記のエラーが出てきた。unhashable typeって出てきた。

 

エラー発生箇所

以下のコードがエラーの発生源だった。

print(type({{ cart_obj.subtotal }}))

他にもこのコードも同様なエラーが出た。

charge = stripe.Charge.create(
    amount      = {{ cart_obj.subtotal }},
    currency    = "usd",
    description ='Example charge',
    source=token,
		)

原因

テンプレート上では、{{ hoge }}って書くけど、サーバーサイドのコードに同じことを書くとこのようなエラーが発生する。unhashable typeが出たときはテンプレートで使う記法で書かれている可能性がある。

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