画面の追加の仕方
手順
Fileメニューから"New"を選択する。
次に"Activity"を選択する。
次に"Empty Activity"を選択すれば追加できる。
ダイアログを実装する
ダイアログについて
まずダイアログとは、画面上に出てくるボックスのことで何かを知らせるとともに、ユーザーに複数のボタンを押してもらうことでアクションを起こしてもらうものである。
トーストもユーザーに何かを通知する機能は同じだが、通知するのみでユーザーがアクションを起こせないという点が異なる。
ダイアログを実装する
実装する概要、イメージ
- ダイアログのボックスを作る
- どこかをタップして表示させる仕組み(リスナ、ハンドラ)を作成する
- リストビューにリスナを設置する
ダイアログのボックスを作る
ダイアログのボックスを作るとは、ダイアログのタイトルとかメッセージ、ボタンを作ることを意味する。
ただし、自分自身で直接ダイアログのボックスを作成するわけではない。自分自身で行うことはダイアログビルダを生成し、そのビルダにどのようなダイアログを作りたいのか教え、ビルダのメソッド(create)を通じてダイアログ(ボックス)を作成する。したがって、ダイアログを作成するとは、ダイアログビルダインスタンスを生成し、ビルダのcreateメソッドを実行することを意味する。
具体的なコードは以下のとおりである。
#ダイアログのボックスを作成する方法 #ダイアログビルダを生成する val builder = AlertDialog.Builder(activity) #ダイアログビルダを通じてダイアログインスタンスを生成する val dialog = builder.create()
しかし待ってほしい。どこにこのビルダのコードを記述するのか。それもきちんと決まっているようだ。
ビルダを記述する場所は、DialogFragmentを継承したクラスの中で記述する。そしてビルダでダイアログを記述したあとにはそのダイアログを返すことも決まっている。ということで、以下のようになる。
#ダイアログのボックスを作成する方法(ビルダの記述する場所も含めて) class MyDialogFragment : DialogFragment(){ override fun onCreateDialog(savedInstanceState:Bundle?):Dialog{ //ダイアログビルダを生成 val builder = AlertDialog.Builder(activity) //ダイアログのタイトルを設定 builder.setTitle(R.string.dialog_title) //ダイアログのメッセージを設定 builder.setMessage(R.string.dialog_message) //ダイアログのポジティブボタンを設定 builder.setPositiveButton(R.string.dialog_btn, DialogButtonClickListener()) //ダイアログビルダを通じてダイアログインスタンスを生成する val dialog = builder.create() return dialog } }
リストビューにリスナを設置する
リストビューにリスナを設置するのは、class MainActivity: AppCompatActivity(){}内に以下の内容を追記する。
lv_obj.onItemClickListener = MyListener()
ButtonとListViewのリスナーについて
ButtonとListViewにつけるリスナーが紛らわしいので整理
Button
ButtonにつけるリスナーはsetOnClickListenerである。
そしてListenerインスタンスはView.OnClickListenerを使えば良い。
使用例 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val btClick = findViewById<Button>(R.id.btClick) val listener = TapOnClickListener() btClick.setOnClickListener(listener) } private inner class TapOnClickListener:View.OnClickListener{ override fun onClick(view:View){ val input = findViewById<EditText>(R.id.etName) val inputStr = input.text.toString() val output = findViewById<TextView>(R.id.tvOutput) } } }
ListView
ListViewにつけるリスナーは、onItemClickListenerである。
そしてListenerインスタンスはAdapterView.OnItemClickListenerを使えば良い。
使用例 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val lvMenu = findViewById(R.id.lvMenu) lvMenu.onItemClickListener = ListItemClickListener() } private inner class ListItemClickListener: AdapterView.OnItemClickListener{ override fun onItemClick(parent:AdapterView<*>, view: View, position: Int, id: Long) { val item = parent.getItemAtPosition(position) as String Toast.makeText(applicationContext, item, Toast.LENGTH_LONG).show() } } }
メモ
androidのバックエンドとしてdjangoが使える
ソース:https://www.slideshare.net/kaki_k/jaws-ug-geeklab?next_slideshow=1
python-gcmについて
android開発にpython-gcmなるものが必要である可能性が出てきた。
これはpush通知に必要な機能だと考えられる。進んできたらまた調べるかもしれない。
https://pypi.org/project/python-gcm/
フッターを画面下に表示させる
参考:https://public-constructor.com/footer-at-bottom-with-flexbox/
結論
<body > <div class="container-fluid mx-0 px-0 test"> {% include 'config/include/navbar.html' %} {% block content %} {% endblock %} <div class="espacio"> </div> {% include 'config/include/bottom.html' %} </div>
.test { display: flex; flex-flow: column; min-height: 100vh; } .espacio{ flex: 1; } footer { background: #333333; color: #ffffff; height: 80px; }
django-allauth環境下でメールアドレスを変更する
条件
メールアドレスでユーザー認証を行う場合に、UserモデルかEmail Addressモデルのどちらのデータを使ってユーザー認証を行っているかわからない。
またEmail Addressインスタンスを変更するとUserモデルインスタンのEメールも自動的に変更されるのかもわからない。この辺を明らかにしていきたい。
検証
Userモデルのemailアドレスをadminから変更する。そして変更後のemailアドレスを使ってログインを試みる。
結果
Userモデルのemailアドレスを変更したところ、変更したアドレスとパスワードを使ってユーザー認証することができた。
また変更前のアドレスとパスワードを使ってもユーザー認証づる事ができた。
結論
Authentication and AuthorizationのUserモデルのemailaddressでもユーザー認証できるし、AccountsのEmail Addressモデルのemailaddressでもユーザー認証できる。したがってemailアドレスを変更する場合には両方とも変更しなければ、セキュリティに問題が生じる。
django Userモデルのusernameを変更させる
各ユーザーがユーザーネームを自由に変更できるようにしたい。時間がかかってしまったので記録しておく。
概要
django-allauthを使っている。ユーザー認証及び登録はemailアドレスで行っている。
そのためusernameはemailアドレスの@以前の部分が自動的に割り当てられる。当初usernameの変更のためにemailアドレスを変更する必要があるかもしれないと思っていたが、django.contrib.auth.models.Userモデルのインスタンスを直接変更すれば済む結論が得られた。
手続き
usernameを変更する際にdjango-allauthまたはビルトインのUserバリデーションを利用したいと考えた。そのためusernameの仕組みを少し調べたが詳しくはわからなかった。ただhttps://github.com/django/django/blob/master/django/contrib/auth/forms.py#L81にUserCreationFormがあり、それを参考に今回はusernameの変更を試みた。
forms.py... from django import forms from django.contrib.auth.models import User class UsernameChangeForm(forms.ModelForm): class Meta: model = User fields = ("username",) field_classes = {'username': UsernameField} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs['class'] = "form-control" field.widget.attrs['placeholder'] = "150 characters or fewer. Letters, digits and @/./+/-/_ only."
views.py... from django.shortcuts import render, redirect from django.views.generic import View from django.contrib.auth.models import User from django.contrib import messages from app.forms import UsernameChangeForm class UsernameChangeView(View): def get(self, request, *args, **kwargs): context = {} form = UsernameChangeForm() context["form"] = form return render(request, 'app/change_username.html', context) def post(self, request, *args, **kwargs): context = {} form = UsernameChangeForm(request.POST) if form.is_valid(): username = form.cleaned_data["username"] user_obj = User.objects.get(username=request.user.username) user_obj.username = username user_obj.save() messages.info(request,"usernameを変更しました。") return redirect('profiles:profile') else: context["form"] = form return render(request, 'config/change_username.html', context)
django FB, Googleアカウントでユーザー認証を実現する
ユーザー認証を実現するには、いくつかのライブラリがありそれを利用すれば良い事がわかった。
https://simpleisbetterthancomplex.com/tutorial/2016/10/24/how-to-add-social-login-to-django.html
https://scotch.io/tutorials/django-authentication-with-facebook-instagram-and-linkedin
https://hodalog.com/oauth-authentication-in-dja/
今回はdjango-allauthを使って実装する。実際試してみてかなり簡単だと思った。
django-allauthのドキュメント
https://django-allauth.readthedocs.io/en/latest/providers.html
settings.pyの記述
#INSTALLED_APPSに記述する内容 'allauth', 'allauth.account', 'allauth.socialaccount', 'allauth.socialaccount.providers.google',#googleを使う場合はこれを追加する 'allauth.socialaccount.providers.facebook',#facebookを使う場合はこれを追加する #social account providerの設定もsettings.pyに記述する SOCIALACCOUNT_PROVIDERS = { 'facebook': { 'METHOD': 'oauth2', 'SCOPE': ['email', 'public_profile', 'user_friends'], 'AUTH_PARAMS': {'auth_type': 'reauthenticate'}, 'INIT_PARAMS': {'cookie': True}, 'FIELDS': [ 'id', 'email', 'name', 'first_name', 'last_name', 'verified', 'locale', 'timezone', 'link', 'gender', 'updated_time', ], 'EXCHANGE_TOKEN': True, 'LOCALE_FUNC': 'path.to.callable', 'VERIFIED_EMAIL': False, 'VERSION': 'v2.12', }, 'google': { 'SCOPE': [ 'profile', 'email', ], 'AUTH_PARAMS': { 'access_type': 'online', } } }
login機能を実装するテンプレートに記述する内容
#テンプレートタグをhtmlファイル上部に記述する {% load socialaccount %} {% providers_media_js %} <a href="{% provider_login_url "facebook" method="js_sdk" %}">Login Button image</a>
Facebookのログイン機能を実装するときに困ったら
エラーメッセージを表示させることが解決の一歩だが、どのように表示させるかを記録しておく。
まずSNSアカウントログインエラーを表示するテンプレートは、allauth/socialaccount/authentication_error.htmlである。
このテンプレートに{{ auth_error }}を記述すると、テンプレートが表示されたときにエラーメッセージも表示されることになる。
この表示されたエラーメッセージをもとにググれば解決するだろう。
参考:https://stackoverflow.com/questions/36728887/debugging-django-allauth-social-network-login-failure/36728888#36728888
facebook developer サイトでの設定事項について
うまくログインができない場合は以下の設定がなされているか確認すると良い。
Settings Basic
- App Domains: "AnySite.com"
- Privacy policy URL: "https://AnySite.com/myprivacy/"
- Website: "https://AnySite.com/"
Settings Advanced
- Server IP Whitelist: let it blank
- Domain Manager: let it blank
Facebook login Settings
Yes Client OAuth Login
Yes Web OAuth Login
Yes (new: forced) Use strict Mode for redicect URLs
Yes Embeded Browser OAuth Login
Yes Enforce HTTPS
Valid OAuth Redirect URLs: "https://AnySite.com/accounts/facebook/login/callback/" (mandatory)
参考:https://stackoverflow.com/questions/49350221/django-2-0-allauth-facebook-2018?noredirect=1&lq=1
GoogleのOAAUTHを使ったログイン機能を実装する
googleのOAauthログイン機能をdjango-allauthで実装すると割と簡単にできる。それを少しまとめておく。
実装の手続き概要:
- デプロイするならドメインを作成する
- GoogleAPIConsoleでプロジェクトを作成する
- Googleアカウント連携に必要なクライアントIDとシークレットキーを入手します
- django-allauthのsettings.pyにgoogleの関連項目を記述する。
- adminページでgoogleのクライアントIDとシークレットキーを使用する。またSITEも編集する。
デプロイ環境でgoogleのログイン機能を実現するには、ドメインが必要になる。localhost:8000のまましようとするとエラーが出てしまうので注意する。
ドメインはfreenomで無料で取得できる。
GoogleAPIConsoleへ移動する。
テンプレートのログインURLについて
django-allauthをインストールしたallauth/account/login.htmlにはsocialログインの部分が記載されておらず、記載されているところは、allauth/socialaccount/snippets/provider_list.htmlである。これがインクルードタグを通じてhtmlに表示される。
{% for provider in socialaccount_providers %} <a title="{{provider.name}}" class="socialaccount_provider {{provider.id}} btn btn-primary" href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">{{provider.name}}</a> {% endfor %}
問題はここにある。以下の記事でBootstrap-socialという便利なcssがある事がわかった。これを利用するにはclass属性にgoogleやfacebook等の内容を記載しなければならない。だからここを直接記載するためにgoogle,Facebookのリンクを表示させるにはいかに書けばいいかという問題が残る。
Bootstrap-socialの紹介記事:
https://medium.com/@jinkwon711/django-allauth-facebook-login-b536444cbc6b
Bootstrap-socialの実装手続きの記事:
https://pisuke-code.com/css-how-to-use-bootstrap-social/
以下の記事によるとgoogle,Facebookは以下のように書けばいいようだ。
<a href="{% provider_login_url "twitter" %}">Twitterでログイン</a> <a href="{% provider_login_url "google" %}">Googleでログイン</a> <a href="{% provider_login_url "facebook" %}">Facebookでログイン</a>
https://tech.torico-corp.com/blog/django-projects-oauth2/
これでうまくいくと思われる
https化してみる
textareaフォームの大きさを設定する
textareaフォームを使う際にそのフォームの大きさを設定したいときがある。自分がよく使っているBootstrapではうまく対応できなかったのでその対応できなかった理由と対処方法をメモとして残す。
Bootstrapで対応できない理由
Bootstrapのsizingのところで様々な要素を変更することができる。
しかしこのサイジングを使うとサイズ変更に伴う空白部分ができてしまう。そこの空白スペースに別要素を入れようとしても入れることができなかった。
したがってwebの少ない画面を有効利用しようとする意図に反する結果となり、採用しなかった。
https://getbootstrap.com/docs/4.4/utilities/sizing/
他の方法
htmlのtextareaフォームの初設定が有る。それがrowsとcolsであった。これを直接設定すれば、空白部分を作らずにフォームのサイズを変更する事ができた。
http://www.htmq.com/html/textarea.shtml
django 独自のドメインを設定する
freenomでドメインを取得する
freenomでは無料でドメインを取得することができる。
取得後にfreenomのmydomainsを開き、Manage Domainを選択する。
Management ToolsからNameserversを選択する。
nameserver1とnameserver2にさくらVPSの情報を入力する。具体的にはNameserver1に「NS1.DNS.NE.JP」、Nameserver2に「NS2.DNS.NE.JP」を入力する。
さくらVPSで取得したドメインを登録する
djangoのsettings.pyを変更する
settings.pyのALLOWED_HOSTにドメインを入力する。
nginxの設定項目を変更する
vi /etc/nginx/conf.d/project.conf
server { listen 80; #server_name 153.126.216.172; server_name 'domainを記入する'; location /static { alias /usr/share/nginx/html/static; } location /media { alias /usr/share/nginx/html/media; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_set_header X-Forwarded-Proto $scheme; } }
Django:ModelChoiceFieldを使う
ドキュメント:https://docs.djangoproject.com/ja/2.2/ref/forms/fields/#modelchoicefield
現在困っているのは、Formにdjango.contrib.auth.models.Userオブジェクトを渡してフォームから別のオブジェクトを生成しようと試みているが、form.is_valid()でうまく行かない。この状況でmodelchoicefieldなる存在を知った。これが解決の糸口になるかもしれないので少々調べてみることとする。
https://www.subthread.co.jp/blog/20160531/
こちらの記事によるとやはりオブジェクトをそのまま利用するために使われるようだ。
使い方を調べる
基本的にforms.Formでmodelchoicefieldを使う記事が多い。しかしながらforms.ModelFormでも使用することができるようだ。それは以下の記事を参照
https://natsumesouxx.hatenadiary.org/entry/20100523/1274565168
from django.forms.models import ModelChoiceField class CustomChoiceField(forms.ModelChoiceField): class BlogForm(ModelForm): user_obj = CustomChoiceField(queryset=User.objects.all()) class Meta: model = Contact
ModelChoiceFieldを動的に使う場合の注意点
ModelChoiceFieldを動的に使う場合には、forms.pyにおけるModelFormのクラス定義に注意しなければならない。
動的に使うとは、views.pyでformインスタンスを生成し、views内で呼び出したquerysetをformインスタンスにあてがい動的な仕様に変更する。
views.py from django.shortcuts import render from django.views.generic import View from products.forms import TestModelForm class TestView(View): def get(self, request, *args, **kwargs): context = {} form = TestModelForm() form.fields['name'] = Products.objects.filter(price=100) context["form"] = form return render(request, "test_model/test_modelchoice.html"
しかしながらforms.pyを以下のようにnoneを使ってしまうと、views.pyでエラーが出てしまう。理由はよくわからない。
forms.py form django import forms from django.forms.models import ModelChoiceField from products.models import Product class TestModelForm(forms.ModelForm): name = ModelChoiceField(queryset=Products.objects.none()) class Meta: model = Product fields = ("name", "category", "price")
必ずall()を使うこと。
forms.py form django import forms from django.forms.models import ModelChoiceField from products.models import Product class TestModelForm(forms.ModelForm): name = ModelChoiceField(queryset=Products.objects.all()) class Meta: model = Product fields = ("name", "category", "price")