Source code for oscar.apps.customer.forms

import datetime
import string

from django import forms
from django.conf import settings
from django.contrib.auth import forms as auth_forms
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.password_validation import validate_password
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ValidationError
from django.utils.crypto import get_random_string
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.translation import gettext_lazy as _
from django.utils.translation import pgettext_lazy

from oscar.apps.customer.utils import get_password_reset_url, normalise_email
from oscar.core.compat import existing_user_fields, get_user_model
from oscar.core.loading import get_class, get_model, get_profile_class
from oscar.core.utils import datetime_combine
from oscar.forms import widgets

CustomerDispatcher = get_class("customer.utils", "CustomerDispatcher")
ProductAlert = get_model("customer", "ProductAlert")
User = get_user_model()


def generate_username():
    letters = string.ascii_letters
    allowed_chars = letters + string.digits + "_"
    uname = get_random_string(length=30, allowed_chars=allowed_chars)
    try:
        User.objects.get(username=uname)
        return generate_username()
    except User.DoesNotExist:
        return uname


[docs]class PasswordResetForm(auth_forms.PasswordResetForm): """ This form takes the same structure as its parent from :py:mod:`django.contrib.auth` """
[docs] def save(self, *args, domain_override=None, request=None, **kwargs): """ Generates a one-use only link for resetting password and sends to the user. """ site = get_current_site(request) if domain_override is not None: site.domain = site.name = domain_override for user in self.get_users(self.cleaned_data["email"]): self.send_password_reset_email(site, user, request)
def send_password_reset_email(self, site, user, request=None): extra_context = { "user": user, "site": site, "reset_url": get_password_reset_url(user), "request": request, } CustomerDispatcher().send_password_reset_email_for_user(user, extra_context)
[docs]class EmailAuthenticationForm(AuthenticationForm): """ Extends the standard django AuthenticationForm, to support 75 character usernames. 75 character usernames are needed to support the EmailOrUsername authentication backend. """ username = forms.EmailField(label=_("Email address")) redirect_url = forms.CharField(widget=forms.HiddenInput, required=False) def __init__(self, host, *args, **kwargs): self.host = host super().__init__(*args, **kwargs) def clean_redirect_url(self): url = self.cleaned_data["redirect_url"].strip() if url and url_has_allowed_host_and_scheme(url, self.host): return url
[docs]class ConfirmPasswordForm(forms.Form): """ Extends the standard django AuthenticationForm, to support 75 character usernames. 75 character usernames are needed to support the EmailOrUsername authentication backend. """ password = forms.CharField(label=_("Password"), widget=forms.PasswordInput) def __init__(self, user, *args, **kwargs): super().__init__(*args, **kwargs) self.user = user def clean_password(self): password = self.cleaned_data["password"] if not self.user.check_password(password): raise forms.ValidationError(_("The entered password is not valid!")) return password
[docs]class EmailUserCreationForm(forms.ModelForm): email = forms.EmailField(label=_("Email address")) password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput) password2 = forms.CharField(label=_("Confirm password"), widget=forms.PasswordInput) redirect_url = forms.CharField(widget=forms.HiddenInput, required=False) class Meta: model = User fields = ("email",) def __init__(self, *args, host=None, **kwargs): self.host = host super().__init__(*args, **kwargs) def _post_clean(self): super()._post_clean() password = self.cleaned_data.get("password2") # Validate after self.instance is updated with form data # otherwise validators can't access email # see django.contrib.auth.forms.UserCreationForm if password: try: validate_password(password, self.instance) except forms.ValidationError as error: self.add_error("password2", error)
[docs] def clean_email(self): """ Checks for existing users with the supplied email address. """ email = normalise_email(self.cleaned_data["email"]) if User._default_manager.filter(email__iexact=email).exists(): raise forms.ValidationError( _("A user with that email address already exists") ) return email
def clean_password2(self): password1 = self.cleaned_data.get("password1", "") password2 = self.cleaned_data.get("password2", "") if password1 != password2: raise forms.ValidationError(_("The two password fields didn't match.")) return password2 def clean_redirect_url(self): url = self.cleaned_data["redirect_url"].strip() if url and url_has_allowed_host_and_scheme(url, self.host): return url return settings.LOGIN_REDIRECT_URL
[docs] def save(self, commit=True): user = super().save(commit=False) user.set_password(self.cleaned_data["password1"]) if "username" in [f.name for f in User._meta.fields]: user.username = generate_username() if commit: user.save() return user
[docs]class OrderSearchForm(forms.Form): date_from = forms.DateField( required=False, label=pgettext_lazy("start date", "From"), widget=widgets.DatePickerInput(), ) date_to = forms.DateField( required=False, label=pgettext_lazy("end date", "To"), widget=widgets.DatePickerInput(), ) order_number = forms.CharField(required=False, label=_("Order number"))
[docs] def clean(self): if self.is_valid() and not any( [ self.cleaned_data["date_from"], self.cleaned_data["date_to"], self.cleaned_data["order_number"], ] ): raise forms.ValidationError(_("At least one field is required.")) return super().clean()
[docs] def description(self): """ Uses the form's data to build a useful description of what orders are listed. """ if not self.is_bound or not self.is_valid(): return _("All orders") else: date_from = self.cleaned_data["date_from"] date_to = self.cleaned_data["date_to"] order_number = self.cleaned_data["order_number"] return self._orders_description(date_from, date_to, order_number)
def _orders_description(self, date_from, date_to, order_number): if date_from and date_to: if order_number: desc = _( "Orders placed between %(date_from)s and " "%(date_to)s and order number containing " "%(order_number)s" ) else: desc = _("Orders placed between %(date_from)s and% (date_to)s") elif date_from: if order_number: desc = _( "Orders placed since %(date_from)s and " "order number containing %(order_number)s" ) else: desc = _("Orders placed since %(date_from)s") elif date_to: if order_number: desc = _( "Orders placed until %(date_to)s and " "order number containing %(order_number)s" ) else: desc = _("Orders placed until %(date_to)s") elif order_number: desc = _("Orders with order number containing %(order_number)s") else: return None params = { "date_from": date_from, "date_to": date_to, "order_number": order_number, } return desc % params def get_filters(self): date_from = self.cleaned_data["date_from"] date_to = self.cleaned_data["date_to"] order_number = self.cleaned_data["order_number"] kwargs = {} if date_from: kwargs["date_placed__gte"] = datetime_combine(date_from, datetime.time.min) if date_to: kwargs["date_placed__lte"] = datetime_combine(date_to, datetime.time.max) if order_number: kwargs["number__contains"] = order_number return kwargs
[docs]class UserForm(forms.ModelForm): def __init__(self, user, *args, **kwargs): self.user = user kwargs["instance"] = user super().__init__(*args, **kwargs) if "email" in self.fields: self.fields["email"].required = True
[docs] def clean_email(self): """ Make sure that the email address is always unique as it is used instead of the username. This is necessary because the uniqueness of email addresses is *not* enforced on the model level in ``django.contrib.auth.models.User``. """ email = normalise_email(self.cleaned_data["email"]) if ( User._default_manager.filter(email__iexact=email) .exclude(id=self.user.id) .exists() ): raise ValidationError(_("A user with this email address already exists")) # Save the email unaltered return email
class Meta: model = User fields = existing_user_fields(["first_name", "last_name", "email"])
Profile = get_profile_class() if Profile: class UserAndProfileForm(forms.ModelForm): def __init__(self, user, *args, **kwargs): try: instance = Profile.objects.get(user=user) except Profile.DoesNotExist: # User has no profile, try a blank one instance = Profile(user=user) kwargs["instance"] = instance super().__init__(*args, **kwargs) # Get profile field names to help with ordering later profile_field_names = list(self.fields.keys()) # Get user field names (we look for core user fields first) core_field_names = set([f.name for f in User._meta.fields]) user_field_names = ["email"] for field_name in ("first_name", "last_name"): if field_name in core_field_names: user_field_names.append(field_name) user_field_names.extend(User._meta.additional_fields) # Store user fields so we know what to save later self.user_field_names = user_field_names # Add additional user form fields additional_fields = forms.fields_for_model(User, fields=user_field_names) self.fields.update(additional_fields) # Ensure email is required and initialised correctly self.fields["email"].required = True # Set initial values for field_name in user_field_names: self.fields[field_name].initial = getattr(user, field_name) # Ensure order of fields is email, user fields then profile fields self.field_order = user_field_names + profile_field_names self.order_fields(self.field_order) class Meta: model = Profile # pylint: disable=modelform-uses-exclude exclude = ("user",) def clean_email(self): email = normalise_email(self.cleaned_data["email"]) users_with_email = User._default_manager.filter( email__iexact=email ).exclude(id=self.instance.user.id) if users_with_email.exists(): raise ValidationError( _("A user with this email address already exists") ) return email def save(self, *args, **kwargs): user = self.instance.user # Save user also for field_name in self.user_field_names: setattr(user, field_name, self.cleaned_data[field_name]) user.save() return super().save(*args, **kwargs) ProfileForm = UserAndProfileForm else: ProfileForm = UserForm
[docs]class ProductAlertForm(forms.ModelForm): email = forms.EmailField( required=True, label=_("Send notification to"), widget=forms.TextInput(attrs={"placeholder": _("Enter your email")}), ) def __init__(self, user, product, *args, **kwargs): self.user = user self.product = product super().__init__(*args, **kwargs) # Only show email field to unauthenticated users if user and user.is_authenticated: self.fields["email"].widget = forms.HiddenInput() self.fields["email"].required = False
[docs] def save(self, commit=True): alert = super().save(commit=False) if self.user.is_authenticated: alert.user = self.user alert.product = self.product if commit: alert.save() return alert
[docs] def clean(self): cleaned_data = self.cleaned_data email = cleaned_data.get("email") if email: try: ProductAlert.objects.get( product=self.product, email__iexact=email, status=ProductAlert.ACTIVE, ) except ProductAlert.DoesNotExist: pass else: raise forms.ValidationError( _("There is already an active stock alert for %s") % email ) # Check that the email address hasn't got other unconfirmed alerts. # If they do then we don't want to spam them with more until they # have confirmed or cancelled the existing alert. if ProductAlert.objects.filter( email__iexact=email, status=ProductAlert.UNCONFIRMED ).count(): raise forms.ValidationError( _( "%s has been sent a confirmation email for another product " "alert on this site. Please confirm or cancel that request " "before signing up for more alerts." ) % email ) elif self.user.is_authenticated: try: ProductAlert.objects.get( product=self.product, user=self.user, status=ProductAlert.ACTIVE ) except ProductAlert.DoesNotExist: pass else: raise forms.ValidationError( _("You already have an active alert for this product") ) return cleaned_data
class Meta: model = ProductAlert fields = ["email"]