2022年8月28日日曜日

Django 覚書10

N+1問題のチェックのため、debug toolをインストールしたがエラー

https://stackoverflow-com.translate.goog/questions/72777378/django-debug-toolbar-baseconnectionhandler-all-error?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=sc

どうやら、バージョンを古くしてやるといいということだった。これで解決。

2022年8月27日土曜日

Django 覚書9 delete

クラスで試したがなぜかうまくいかない。 クラスのDeleteViewで削除する例は多いが、関数を利用したものが少ない。

関数だと、こんなふうにしたところ、うまくいった。


@login_required
def mcsmain_delete(request,pk):
    template_name = "mcsmain/mcsmain_delete.html"
    mcsmain = get_object_or_404(Mcsmain, pk=pk)
    if mcsmain.created_by_id != request.user.id:
        return HttpResponseForbidden("このメッセージの削除は許可されていません。")   
    if request.POST:
        mcsmain.delete()
        return redirect('/')
    return render(request,template_name) ←ここで、pkを渡さなくても、postには支障ないようだ。このへんがわかりずらい。

***mscmain_detail.html
<a href="{% url 'mcsmain_delete' mcsmain.id %}">削除</a>

***mscmain_delete.html
<form method="post" >
 {% csrf_token %}
    <p>削除していいですか?</p>
    <input type="submit" value="削除する">
</form>

2022年8月20日土曜日

Django 覚書8

テンプレートを、登録と編集で2つ用意したが、編集用がなぜか、viewで、テンプレート指定しているが存在しないというエラー、存在しているのだけれど。
仕方なく、兼用して、formの中身でタイトルだけ切り替えてみた。
formからの取り出し方は、form.nicknameだけではだめで、valueを追加する必要があった。


{% extends "base.html" %}
{% load django_bootstrap5 %}
{% block main %}
 {%if form.nickname.value != "" %} 
  <h2>プロフィールの編集</h2>
 {% else %} 
 <h2>プロフィールの登録</h2>
 {% endif %}
<form method="post" enctype="multipart/form-data" class="p-4 m-4 bg-light border border-success rounded form-groups">
    {% csrf_token %}
    {% bootstrap_form form %}
    {% bootstrap_button button_type="submit" content="保存" %}
</form>
{% endblock %}

2022年8月17日水曜日

Django 覚書7

 ファイルサイズ制限をする方法を調べてみた。(画像を圧縮する実装も、それはそれで大変なので、ファイルサイズ制限にしてみた)

https://www.sea2800.com/?p=513

Modelのほうに、コードを入れるほうがいいようだ。

 community_photo=models.ImageField("コミュニティ画像",upload_to='community_photos/',null=True,blank=True,validators=[file_size])

def file_size(value):
    limit = 500000
    if value.size>limit:
        raise ValidationError('画像サイズは、500KB以下にしてください.')


ただ、Viewでは関数を使っていたので、若干の注意が必要だった。

@login_required
def community_create(request):
    if request.method == "POST":
        form = CommunityForm(request.POST,request.FILES) 
   #defのときは、やはりrequest.FILESが必須のようだ。これがないとうまくいかない。
        if form.is_valid():
            com=form.save(commit=False)
            com.community_photo=request.FILES.get('community_photo')
   .....

2022年8月16日火曜日

Djanog 覚書6 基本のまとめ

 とくにViewとFormあたりで、関数なのか、クラスなのかで、いろいろごちゃまぜになりやすい。関数のほうが、カスタマイズしやすいので、Formと一緒に基本的なことをまとめてみた。

#FORM

class CommunityForm(forms.ModelForm):     
 community_introduction = forms.CharField(widget=forms.Textarea(attrs={'placeholder':'紹介文...'}), max_length=512,label="コミュニティ紹介")       

        model=Community
        fields={'id','community_name','community_photo','community_introduction'}

#VIEW
@login_required
def community_create(request):
    if request.method == "POST":
        form = CommunityForm(request.POST)
        if form.is_valid():
            com=form.save(commit=False)
            com.community_photo=request.FILES.get('community_photo')←画像はこの処理が必要
            com.created_by=request.user ←念のため入れる?
            com.save()
            return redirect(community_detail,pk=com.id)
    else:            
            form = CommunityForm({'created_by':request.user}) ←正しくは、このようにするとOk
            #form.created_by= request.user ←あらかじめ、データを入れておくために、最初、これで試したがだめだった。
なお、CommunityFormのほうでは、fieldにcreated_byは入れないことで表示はしない。けれど、Communityのモデルの処理は行ってくれる。このへんに、注意が必要かも。
    return render(request,'accounts/community_create.html',{'form':form})



@login_required
def community_update(request, pk):
    com = get_object_or_404(Community, id=pk)
    if com.created_by.username != request.user.username:
        return HttpResponseForbidden("この編集は許可されていません。")
    if request.method == "POST":
        form = CommunityForm(request.POST, instance=com)
        if form.is_valid():
            post=form.save()
            post.community_photo=request.FILES.get('community_photo')←画像はこの処理が必要
            post.created_by=request.user ←念のため入れる?
            post.save()
            return redirect('community_list')←ここは、detailでもいいかもしれない
    else:
        form = CommunityForm(instance=com)
    return render(request, 'accounts/community_update.html', {'form': form})

※上記の方法だと、Updateのときに、画像データが消えてしまう。
これを避けるには、以下のように修正するといいようだ。

@login_required
def community_update(request, pk):
    com = get_object_or_404(Community, id=pk)
    if com.created_by.username != request.user.username:
        return HttpResponseForbidden("この編集は許可されていません。")
    if request.method == "POST":
        if not request.FILES:
            request.FILES['community_photo'] = com.community_photo #更新時画像保持
        form = CommunityForm(request.POST, request.FILES, instance=com) #blank=Turue,null=Trueだと、request.FILESが必須とのこと
        if form.is_valid():
            post=form.save()
            post.community_photo=request.FILES.get('community_photo')
            post.created_by=request.user
            post.save()
            return redirect('community_list')
    else:
        form = CommunityForm(instance=com)

    return render(request, 'accounts/community_update.html', {'form': form})



Django 覚書5

 サイトのページで設定を操作するには、最初、テキストファイルにでも保存するのかと思っていたが、あまりそういう方法は取らないようで、やはり、データベースに置くようだ。

https://blog.narito.ninja/detail/104

上記サイトを参考にさせていただいた。これだと、どのページからでも、グローバルでrequest.site.sitedetail.プロパティ というような感じで読み取れるので、非常にありがたい。また、管理サイトで、編集もできる。

 早速、サイトの中でも変更の可能性がありそうな、style変更(colorなど)や説明文等に利用させてもらった。

2022年8月15日月曜日

Django 覚書4

 今回は、フロントエンドの一部をELMでかいてみた。しかし、ちょっとした連係ミスでこまることもある。ELMでブートストラップを使って、ボタンを表示していたが、これが、PCサイズの指定なので、Djangoで、レスポンシブがうまく動作しない。スマホでの表示が、PC表示から切り替わらないという現象が起きた。面倒なので、ELMの表示は、Bootstrapは使わず、すべて、Djangoのほうで統一することにした。

 さらに、やっかいなのが、ブラウザには画像だけでなく、JSのキャッシュも残っているということ、もちろんELMもJSなので、ELMをコンパイルしたはずなのに、すぐ反映してくれない。仕方ないので、ブラウザのキャッシュを削除したら解決。

Django Test 覚書3

 Djangoでは、Testの必要性を感じ始め、少しずつ取り組んでいる。

初歩的ミスから

@login_required
def mcsmain_detail(request, mcsmain_id):
    mcsmain = get_object_or_404(Mcsmain, pk=mcsmain_id)
    comments=....
    return render(...

class SnippetDetailTest(TestCase):
    def setUp(self):
        self.user = UserModel.objects.create(
            username="test_user",....
   ①ここに

    def test_should_use_expected_template(self):
        response = self.client.get("/mcsmain/%s/" % self.mcsmain.id)
        self.assertTemplateUsed(response, "mcsmain/mcsmain_detail.html")

login_requiredなので、エラーが発生、   self.client.force_login(self.user)をテストの①の中に入れる必要がある。
    

2022年8月14日日曜日

Django 覚書2

 ちょっとしたことで、だいぶ時間がかかってしまった。

  {% if request.user.id==item.created_by.id %}では、だめで

  {% if request.user.pk == item.created_by.pk %}が正解だった。テンプレートには、まだなれていない。

2022年8月12日金曜日

Django 覚書

 Djangoの参考書も読んだが、それだけでは実際のシステムを作り上げる際にいろいろな壁にぶつかってしまう。結局、ネットで情報を仕入れたり、工夫しながらやっていくしかない。最近、いくつかきづいたことをまとめてみた。

・メール送信は、必ずしもサードパーティがいいとも限らない。2,3試したがどれも、自分の環境ではうまくいかず、結局、Djangoの基本機能を利用してうまくいった。(フォームメールなども含めて、そのほうがいいようだ)前回の投稿参照のこと

・画像ファイルのアップロードも、圧縮したい。これは、stdimageためしたが、うまくいかない。Djangoのクラスをうまく継承している、imagekitをためしてようやくうまくいった。

 Imageモデルを作成して、それをViewの中で通常の使い方ができる。くせがない感じだ。

 image=Image.objects.all().filter(taken_by=profile_id).first()のように

マッチングサイトの足あと機能は、こんな感じでmap活用してなんとかクリア

class FootprintListView(LoginRequiredMixin,ListView):    
       template_name="accounts/footprint_list.html" 
       paginate_by=4       
       def get_queryset(self):         #なぜか、これを入れないとエラー、このへんが
                    return uquery(self,"" )      #まだ 勉強不足
       def get_context_data(self):
          context = super().get_context_data()
          flist = Footprint.objects.filter(looked=self.request.user) 
          #object_list=uquery(self,"" )
          iobList=map( lambda ob: {'usr':ob.look
                                   ,'img':Image.objects.all().filter(taken_by=ob.look.id).first()
                                   ,'footprintThanksSend':Footprint.objects.all().filter(look=ob.look,looked=self.request.user ).first()
                                     }  ,flist)   
          context['iobject_list']=iobList
          context['cuser']=self.request.user
          context['count']=len(list(iobList))
          return context
・「足あとありがとう」(自分のプロファイル見てくれてありがとう)の機能は、意外と基本的なところで、つまづく。①~③のところは、どうということのないところだが、これを思いつくまでだいぶ時間がかかった。画面のフォームを通して、値を入れていくコード例はたくさんあるが、このように、直接値を入れることは少ないので。

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def footprint_thanks(request,profile_id):
    if request.method == 'GET':           
       thanks_from = request.user.id  # miraretahito 
       thanks_to=User.objects.filter(id=profile_id)[0] #gazo mitahito
       obj=Footprint.objects.filter(look=thanks_to).first() ①
       obj.thanks=True ②
       obj.save()  ③
       return render(request, 'accounts/footprint_thanks.html', {'thanks_to':obj.look})

2022年8月8日月曜日

django リンククリックでユーザー登録するためのメール配信

サードパーティのアプリもあったが、なかなか組み込みが難しかった。djangoのメール配信機能があるので、それを使ってみた。

ポイントは View と 環境変数

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def mail_send(request,profile_id):
    if request.method == 'GET':     
       token=hashlib.sha256((str(profile_id)+"ここには適当な文字をsaltとして入れる").encode('utf-8')).hexdigest()
       if settings.DEBUG==True:        url="http://"+settings.ALLOWED_HOSTS[0]+":8000/accounts/mail_check/"+str(profile_id)+"/?token="+token 
       else:        url="https://"+settings.ALLOWED_HOSTS[1]+"/accounts/mail_check/"+str(profile_id)+"/?token="+token #0がlocal環境、1が2つめ deploy環境 から取得する。      
       subject = "メール登録について"
       message = "メール登録のためのリンク:"+url
       from_email = settings.EMAIL_HOST_USER  # 送信者
       to_email=User.objects.filter(id=profile_id)[0].email
       recipient_list = [to_email]  # 宛先リスト
       send_mail(subject, message, from_email, recipient_list)
       return render(request, 'accounts/mail_send.html', {'to_email':to_email})

@api_view(['GET'])
def mail_check(request,profile_id):
    if request.method == 'GET':     
       token=hashlib.sha256((str(profile_id)+"ここは適当な文字をsaltとして入れる").encode('utf-8')).hexdigest()
       if token==request.GET.get("token")  :
            tokenflg=True
        #ここで、DBのフラグ変更
            cdt=get_object_or_404(User,pk=profile_id)
            cdt.is_kaiin=True
            cdt.save()
       else :
            tokenflg=False
       to_email=User.objects.filter(id=profile_id)[0].email
       return render(request, 'accounts/mail_check.html', {'to_email':to_email,'tokenflg':tokenflg})

***パラメータの取得方法は、request.tokenかと思ったが、そうではなく、request.GET.get("token") だった。このへんは、慣れていくしかない。

settings.py
DEBUG = os.getenv('DEBUG',True) #deploy環境ではDEBUG環境変数をFalseに
ALLOWED_HOSTS = ['localhost','ここに公開サーバドメイン']

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# 環境変数にしておけば、セキュリティも上がり、ローカルとDeploy環境の切り替え負担もない いちおう、ディフォルトはローカルにしてみた
EMAIL_HOST = os.getenv('EMAIL_HOST','smtp.gmail.com')
EMAIL_PORT = os.getenv('EMAIL_PORT',587)
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') 
EMAIL_USE_TLS = True

**その後、この方法だと、レンタルサーバによっては、OSから環境変数がうまく取得できないことがあることが判明、そこでdjango-environというものを使わせていただいた。これだと、うまく動作してくれた。

**さらに、追加情報
 ローカルからはだいじょうぶだったが、公開先のサーバでは、djangoからのメール送信元がwebmaster@localhostとなり、エラーが発生する。 
 https://teratail.com/questions/255401 の情報によれば、DEFAULT_FROM_EMAIL に、送信元をきちんと設定しておくことで、解決した。いろいろ、知らない情報も、ネットでググるといいようだ。

2022年8月5日金曜日

django Formで外部キーの内容の選択範囲をフィルタリングするには

https://qiita.com/44d/items/897e5bb20113315af006 を参考にさせていただいた。

マッチングアプリで、「いいね」をお互いに交換できた人のみ、選択できるようにしてみた。
ok_listは、前回考えたアルゴリズムをそのまま利用した。
ポイントは、 form.fields['receiver'].queryset = User.objects.filter(id__in= ok_list(request)) の部分。


@login_required
def dmessage_new(request):
    if request.method == 'POST':
        form = DmessageForm(request.POST)
        if form.is_valid():
            dmessage = form.save(commit=False)
            dmessage.sender = request.user
            dmessage.save()
            return redirect(dmessage_detail, dmessage_id=dmessage.pk)
    else:
        form = DmessageForm()    
        form.fields['receiver'].queryset = User.objects.filter(id__in= ok_list(request))
    return render(request, "dmessages/dmessage_new.html", {'form': form})

def ok_list(request):
          like_list = Matching.objects.filter(  Q(approaching__username__icontains=request.user) )
          liked_list = Matching.objects.filter(  Q(approached__username__icontains=request.user) )
          return (like_list.values_list('approached',flat=True)).intersection(liked_list.values_list('approaching',flat=True))

2022年8月4日木曜日

古いソフトの実行

 win11にしたら、win10まで動いていた古いソフトが動かなくなる。vbrjp200.dllなど、何十年前のVBのランタイムだが、ついにこれだけで動かなくなってしまった。が、よく調べると、otvdmという便利なものがあった。うまく動作してくれた。

djangoをローカルネットでサーバーに

 windowsでdjangoを、ローカル内で使えるか試してみた。

・ファイアーウオールの受信ポート8000を開けてみた。

 ルータのフィルタリングも念のため、確認

これだけではだめだった。

・djangoのallow hostsは['192.....']の形式で設定

・起動は python manage.py runsrver 192.....:8000 とする

この2点をうっかりすると忘れやすい。

2022年8月1日月曜日

djangoでもpythonの関数型機能が役立つ

 マッチングの機能を作成してみた。DjangoのListViewで相手を選択する、選択されるのリスト。マッチしたユーザーのリストを取得するには、どうするかで、けっこうはまる。

ようやく解決方法をみつける。pythonのmap関数、ラムダ式が役立った。

  def get_context_data(self):
          context = super().get_context_data()
          like_list = Matching.objects.filter(  Q(approaching__username__icontains=self.request.user) )
          liked_list = Matching.objects.filter(  Q(approached__username__icontains=self.request.user) )

          context['like_list']=like_list
          context['liked_list']=liked_list
          tmplist=(like_list.values_list('approached',flat=True)).intersection(liked_list.values_list('approaching',flat=True))
          context['ok_list']=list(map( lambda ob: User.objects.get(id=ob) ,tmplist))
         
          return context