Django Userモデル以外でパスワード認証

DjangoでUserモデル以外にパスワードを持つモデルを定義して認証したいことがあった。


```python:admin.py
class AdminName(admin.ModelAdmin):
    readonly_fields = ('password',) #1
    form = MyForm
    fieldsets = [
        (None, {'fields':('name',)}),
        (None, {'fields':('password',),'description':'id'}) #2
    ]

    def get_object(self, request, view, object_id):
        obj = super(AdminName, self).get_object(request,view, object_id) #3
        if obj:
            url = reverse('change_password', args=[obj.id])
            # objが存在する場合、そのidを設定します
            self.fieldsets[1][1]['description'] = '生のパスワードは格納されていないため、このパスワードを確認する方法はありません。\
            しかし'+f'<a href="{url}">このフォーム</a>'+'を使用してパスワードを変更できます。' #4
        return obj

# Register your models here.
admin.site.register(ModelName, AdminName)
```

#1 編集させないリードオンリーなフィールドを記述
#2 'description' で注釈をつけられる。
#3 adminサイトのchange urlに含まれるidを取得する。
#4 文字列の中にhtmlを仕込むことによりリンクを作成


```python:forms.py
class SetPasswordForm(forms.Form):
     error_messages = {
        "password_mismatch": _("The two password fields didn't match."),
    }
    new_password1 = forms.CharField(
        label=_("New password"),
        widget=forms.PasswordInput(attrs={"autocomplete": "new-password", 'class': 'form-control'}),
        strip=False,
        help_text=password_validation.password_validators_help_text_html(),
    )
    new_password2 = forms.CharField(
        label=_("New password confirmation"),
        strip=False,
        widget=forms.PasswordInput(attrs={"autocomplete": "new-password", 'class': 'form-control'}),
    )
 
    def __init__(self, index, *args, **kwargs):
        self.index = index
        super().__init__(*args, **kwargs)
 
    def clean_new_password2(self):
        password1 = self.cleaned_data.get("new_password1")
        password2 = self.cleaned_data.get("new_password2")
        if password1 and password2:
            if password1 != password2:
                raise ValidationError(
                    self.error_messages["password_mismatch"],
                    code="password_mismatch",
                )
        password_validation.validate_password(password2)
        return password2
```

formのcleanメソッドを使用する。たくさん資料があるのでここでは説明しない。


```python:views.py
# adminサイトのChatTypeパスワード変更
class PasswordContextMixin:
    extra_context = None
 
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context.update({"title": self.title, **(self.extra_context or {})})
        return context
        

class PasswordResetView(FormView):
    template_name = 'my_password_change_form.html' #1
    form_class = SetPasswordForm
    success_url = reverse_lazy('password_change_done')

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['index'] = self.kwargs['index']  # Assuming you pass 'index' as a parameter in the URL
        return kwargs

    def form_valid(self, form):
        index = form.index  # Access the index from the form
        # You can perform any additional processing or validation here if needed
        # For example, you can update the user's password and log them in
        col = Model.objects.get(id=index)
        col.password = make_password(form.cleaned_data['new_password1']) #2
        col.save()
        return super().form_valid(form)

    def form_invalid(self, form):
        # Handle invalid form submission here if needed
        return super().form_invalid(form)
    

class PasswordChangeDoneView(PasswordContextMixin, TemplateView): #3
    template_name = "my_password_change_done.html"
    title = _("Password change successful")
```

#1 admin標準のテンプレートを基に作成
#2 djangoのmake_passwordを用いてパスワードを暗号化
#3 パスワード変更成功後のView