Changeset View
Changeset View
Standalone View
Standalone View
bid_main/forms.py
| import logging | import logging | ||||
| import pathlib | import pathlib | ||||
| from django import forms | from django import forms | ||||
| from django.conf import settings | from django.conf import settings | ||||
| from django.contrib.auth import forms as auth_forms | from django.contrib.auth import forms as auth_forms, password_validation, authenticate | ||||
| from django.forms import ValidationError | from django.forms import ValidationError | ||||
| from django.template import defaultfilters | from django.template import defaultfilters | ||||
| from django.utils import timezone | from django.utils import timezone | ||||
| from django.utils.translation import ugettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||
| from .models import User | from .models import User | ||||
| from .recaptcha import check_recaptcha | |||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||
| class BootstrapModelFormMixin: | class BootstrapModelFormMixin: | ||||
| """Adds Bootstrap CSS classes to all form fields.""" | """Adds Bootstrap CSS classes to all form fields.""" | ||||
| def __init__(self, *args, **kwargs): | def __init__(self, *args, **kwargs): | ||||
| super().__init__(*args, **kwargs) | super().__init__(*args, **kwargs) | ||||
| kwargs.setdefault("label_suffix", "") | kwargs.setdefault("label_suffix", "") | ||||
| for field_name, field in self.fields.items(): | for field_name, field in self.fields.items(): | ||||
| # Add Bootstrap CSS 'form-control' class to style form fields | # Add Bootstrap CSS 'form-control' class to style form fields | ||||
| # Checkbox inputs are an exception and require a 'form-check-input' class | # Checkbox inputs are an exception and require a 'form-check-input' class | ||||
| # More information on form classes https://getbootstrap.com/docs/4.3/components/forms/ | # More information on form classes https://getbootstrap.com/docs/4.3/components/forms/ | ||||
| if field.widget.input_type == "checkbox": | if field.widget.input_type == "checkbox": | ||||
| field.widget.attrs["class"] = "form-check-input" | field.widget.attrs["class"] = "form-check-input" | ||||
| else: | else: | ||||
| field.widget.attrs["class"] = "form-control" | field.widget.attrs["class"] = "form-control" | ||||
| class RegistrationForm(forms.ModelForm): | |||||
| """Register a new user and log them in immediately. | |||||
| This class uses 'bid_main/register.html' to render. | |||||
| """ | |||||
| class Meta: | |||||
| model = User | |||||
| fields = ["full_name", "email", "agree_privacy_policy", "password"] | |||||
| password = forms.CharField( | |||||
| label=_("Password"), | |||||
| widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}), | |||||
| strip=False, | |||||
| help_text=password_validation.password_validators_help_text_html(), | |||||
| required=True, | |||||
| ) | |||||
| password_confirm = forms.CharField( | |||||
| label=_("Confirm password"), | |||||
| strip=False, | |||||
| widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}), | |||||
| required=True, | |||||
| ) | |||||
| recaptcha_token = forms.CharField(widget=forms.HiddenInput(), required=True) | |||||
| agree_privacy_policy = forms.BooleanField( | |||||
| initial=False, | |||||
| required=True, | |||||
| error_messages={ | |||||
| "required": _( | |||||
| "It is not possible to register a Blender ID account " | |||||
| "without agreeing to the privacy policy" | |||||
| ) | |||||
| }, | |||||
| help_text="Check to agree with our privacy policy", | |||||
| ) | |||||
| def __init__(self, *args, **kwargs): | |||||
| """Keep request to be used later in reCAPTCHA validation and during authentication.""" | |||||
| self.request = kwargs.pop('request', None) | |||||
| super().__init__(*args, **kwargs) | |||||
| # UserModel allows blank full_name because of API user create endpoint, | |||||
| # but full name is required during normal registration flow. | |||||
| self.fields['full_name'].required = True | |||||
| def clean_password_confirm(self): | |||||
| """Compare and validate both password fields.""" | |||||
| password = self.cleaned_data.get('password') | |||||
| password_confirm = self.cleaned_data.get('password_confirm') | |||||
| if password and password_confirm: | |||||
| if password != password_confirm: | |||||
| raise forms.ValidationError( | |||||
| auth_forms.SetPasswordForm.error_messages['password_mismatch'], | |||||
| code='password_mismatch', | |||||
| ) | |||||
| return password_confirm | |||||
| def clean_recaptcha_token(self): | |||||
| """Check reCAPTCHA, if it's configured in the settings. | |||||
| This form is using score-based reCAPTCHA v3: | |||||
| https://developers.google.com/recaptcha/docs/v3 | |||||
| """ | |||||
| if not settings.GOOGLE_RECAPTCHA_SECRET_KEY: | |||||
| return | |||||
| is_valid, message = check_recaptcha(self.request, self.cleaned_data['recaptcha_token']) | |||||
| if not is_valid: | |||||
| self.add_error('recaptcha_token', forms.ValidationError(message, 'invalid')) | |||||
| def _post_clean(self): | |||||
| """Validate password. Copied from django/contrib/auth/forms.py:UserCreationForm. | |||||
| Validate the password after self.instance is updated with form data by super(). | |||||
| """ | |||||
| super()._post_clean() | |||||
| password = self.cleaned_data.get('password_confirm') | |||||
| if password: | |||||
| try: | |||||
| password_validation.validate_password(password, self.instance) | |||||
| except ValidationError as error: | |||||
| self.add_error('password_confirm', error) | |||||
| def get_user(self): | |||||
| """Get user as if they are logging in with a LoginView. | |||||
| Copied from django/contrib/auth/forms.py:AuthenticationForm. | |||||
| """ | |||||
| email = self.instance.email | |||||
| password = self.instance.password | |||||
| self.user_cache = authenticate(self.request, username=email, password=password) | |||||
| if self.user_cache is None: | |||||
| raise ValidationError( | |||||
| 'Unable to process the registration request', | |||||
| code='invalid_login', | |||||
| ) | |||||
| return self.user_cache | |||||
| class UserRegistrationForm(BootstrapModelFormMixin, forms.ModelForm): | class UserRegistrationForm(BootstrapModelFormMixin, forms.ModelForm): | ||||
| # This class uses 'bid_main/user_register_form.html' to render | # This class uses 'bid_main/user_register_form.html' to render | ||||
| class Meta: | class Meta: | ||||
| model = User | model = User | ||||
| fields = ["full_name", "email", "nickname", "agree_privacy_policy"] | fields = ["full_name", "email", "nickname", "agree_privacy_policy"] | ||||
| agree_privacy_policy = forms.BooleanField( | agree_privacy_policy = forms.BooleanField( | ||||
| initial=False, | initial=False, | ||||
| ▲ Show 20 Lines • Show All 67 Lines • ▼ Show 20 Lines | def __init__(self, *args, **kwargs): | ||||
| self._original_email = instance.email | self._original_email = instance.email | ||||
| super().__init__(*args, **kwargs) | super().__init__(*args, **kwargs) | ||||
| # Refuse to edit the email address while another change is still pending. | # Refuse to edit the email address while another change is still pending. | ||||
| if instance.email_change_preconfirm: | if instance.email_change_preconfirm: | ||||
| self.fields["email"].disabled = True | self.fields["email"].disabled = True | ||||
| def clean_full_name(self): | # UserModel allows blank full_name because of API user create endpoint, | ||||
| full_name = self.cleaned_data["full_name"].strip() | # but full name is required during normal registration flow. | ||||
| if not full_name: | self.fields['full_name'].required = True | ||||
| raise forms.ValidationError(_("Full Name is a required field")) | |||||
| return full_name | |||||
| def clean_email(self): | def clean_email(self): | ||||
| """Don't allow direct changes to the email address, only to the preconfirm one.""" | """Don't allow direct changes to the email address, only to the preconfirm one.""" | ||||
| form_email = self.cleaned_data["email"] | form_email = self.cleaned_data["email"] | ||||
| if self._original_email == form_email: | if self._original_email == form_email: | ||||
| self.log.debug("%s updated their profile", form_email) | self.log.debug("%s updated their profile", form_email) | ||||
| return form_email | return form_email | ||||
| ▲ Show 20 Lines • Show All 134 Lines • Show Last 20 Lines | |||||