plain.password
Password authentication for Plain.
Usage
To enable password authentication in your Plain application, add the PasswordLoginView
to your urls.py
:
# app/urls.py
from plain.urls import path
from plain.passwords.views import PasswordLoginView
urlpatterns = [
path('login/', PasswordLoginView.as_view(), name='login'),
# ...
]
This sets up a basic login view where users can authenticate using their username and password.
FAQs
How do I customize the login form?
To customize the login form, you can subclass PasswordLoginForm
and override its fields or methods as needed. Then, set the form_class
attribute in your PasswordLoginView
to use your custom form.
# app/forms.py
from plain.passwords.forms import PasswordLoginForm
class MyCustomLoginForm(PasswordLoginForm):
# Add custom fields or override methods here
pass
# app/views.py
from plain.passwords.views import PasswordLoginView
from .forms import MyCustomLoginForm
class MyPasswordLoginView(PasswordLoginView):
form_class = MyCustomLoginForm
Update your urls.py
to use your custom view:
# app/urls.py
from plain.urls import path
from .views import MyPasswordLoginView
urlpatterns = [
path('login/', MyPasswordLoginView.as_view(), name='login'),
# ...
]
1from plain import forms
2from plain.auth import get_user_model
3from plain.exceptions import ValidationError
4from plain.models.forms import ModelForm
5
6from .core import check_user_password
7from .hashers import check_password
8
9# class PasswordResetForm(forms.Form):
10# email = forms.EmailField(
11# # label="Email",
12# max_length=254,
13# # widget=forms.EmailInput(attrs={"autocomplete": "email"}),
14# )
15
16# def send_mail(
17# self,
18# subject_template_name,
19# email_template_name,
20# context,
21# from_email,
22# to_email,
23# html_email_template_name=None,
24# ):
25# from plain.mail import EmailMultiAlternatives
26
27# """
28# Send a plain.mail.EmailMultiAlternatives to `to_email`.
29# """
30# template = Template(subject_template_name)
31# subject = template.render(context)
32# # Email subject *must not* contain newlines
33# subject = "".join(subject.splitlines())
34# template = Template(email_template_name)
35# body = template.render(context)
36
37# email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
38# if html_email_template_name is not None:
39# template = Template(html_email_template_name)
40# html_email = template.render(context)
41# email_message.attach_alternative(html_email, "text/html")
42
43# email_message.send()
44
45# def get_users(self, email):
46# """Given an email, return matching user(s) who should receive a reset.
47
48# This allows subclasses to more easily customize the default policies
49# that prevent inactive users and users with unusable passwords from
50# resetting their password.
51# """
52# active_users = get_user_model()._default_manager.filter(email__iexact=email)
53# return (u for u in active_users if _unicode_ci_compare(email, u.email))
54
55# def save(
56# self,
57# subject_template_name="auth/password_reset_subject.txt",
58# email_template_name="auth/password_reset_email.html",
59# use_https=False,
60# token_generator=default_token_generator,
61# from_email=None,
62# html_email_template_name=None,
63# extra_email_context=None,
64# ):
65# """
66# Generate a one-use only link for resetting password and send it to the
67# user.
68# """
69# email = self.cleaned_data["email"]
70# for user in self.get_users(email):
71# context = {
72# "email": user.email,
73# "uid": urlsafe_base64_encode(force_bytes(user.pk)),
74# "user": user,
75# "token": token_generator.make_token(user),
76# "protocol": "https" if use_https else "http",
77# **(extra_email_context or {}),
78# }
79# self.send_mail(
80# subject_template_name,
81# email_template_name,
82# context,
83# from_email,
84# user.email,
85# html_email_template_name=html_email_template_name,
86# )
87
88
89# class SetPasswordForm(forms.Form):
90# """
91# A form that lets a user set their password without entering the old
92# password
93# """
94
95# error_messages = {
96# "password_mismatch": "The two password fields didn’t match.",
97# }
98# new_password1 = forms.CharField(
99# # label="New password",
100# # widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
101# strip=False,
102# # help_text=validators.password_validators_help_text_html(),
103# )
104# new_password2 = forms.CharField(
105# # label="New password confirmation",
106# strip=False,
107# # widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
108# )
109
110# def __init__(self, user, *args, **kwargs):
111# self.user = user
112# super().__init__(*args, **kwargs)
113
114# def clean_new_password2(self):
115# password1 = self.cleaned_data.get("new_password1")
116# password2 = self.cleaned_data.get("new_password2")
117# if password1 and password2 and password1 != password2:
118# raise ValidationError(
119# self.error_messages["password_mismatch"],
120# code="password_mismatch",
121# )
122# validators.validate_password(password2, self.user)
123# return password2
124
125# def save(self, commit=True):
126# password = self.cleaned_data["new_password1"]
127# self.user.set_password(password)
128# if commit:
129# self.user.save()
130# return self.user
131
132
133# class PasswordChangeForm(SetPasswordForm):
134# """
135# A form that lets a user change their password by entering their old
136# password.
137# """
138
139# error_messages = {
140# **SetPasswordForm.error_messages,
141# "password_incorrect": "Your old password was entered incorrectly. Please enter it again.",
142# }
143# old_password = forms.CharField(
144# # label="Old password",
145# strip=False,
146# # widget=forms.PasswordInput(
147# # attrs={"autocomplete": "current-password", "autofocus": True}
148# # ),
149# )
150
151# field_order = ["old_password", "new_password1", "new_password2"]
152
153# def clean_old_password(self):
154# """
155# Validate that the old_password field is correct.
156# """
157# old_password = self.cleaned_data["old_password"]
158# if not self.user.check_password(old_password):
159# raise ValidationError(
160# self.error_messages["password_incorrect"],
161# code="password_incorrect",
162# )
163# return old_password
164
165
166class PasswordLoginForm(forms.Form):
167 email = forms.EmailField(max_length=150)
168 password = forms.CharField(strip=False)
169
170 def clean(self):
171 User = get_user_model()
172
173 email = self.cleaned_data.get("email")
174 password = self.cleaned_data.get("password")
175
176 if email and password:
177 try:
178 # The vast majority of users won't have a case-sensitive email, so we act that way
179 user = User.objects.get(email__iexact=email)
180 except User.DoesNotExist:
181 # Run the default password hasher once to reduce the timing
182 # difference between an existing and a nonexistent user (django #20760).
183 check_password(password, "")
184
185 raise ValidationError(
186 "Please enter a correct email and password. Note that both fields may be case-sensitive.",
187 code="invalid_login",
188 )
189
190 if not check_user_password(user, password):
191 raise ValidationError(
192 "Please enter a correct email and password. Note that both fields may be case-sensitive.",
193 code="invalid_login",
194 )
195
196 self._user = user
197
198 return self.cleaned_data
199
200 def get_user(self):
201 return self._user
202
203
204class PasswordSignupForm(ModelForm):
205 confirm_password = forms.CharField(strip=False)
206
207 class Meta:
208 model = get_user_model()
209 fields = ("email", "password")
210
211 def clean(self):
212 cleaned_data = super().clean()
213 password = cleaned_data.get("password")
214 confirm_password = cleaned_data.get("confirm_password")
215 if password and confirm_password and password != confirm_password:
216 raise ValidationError(
217 "The two password fields didn't match.",
218 code="password_mismatch",
219 )
220 return cleaned_data