You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

forms.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. import string
  2. import random
  3. from six.moves.urllib import parse
  4. from django import forms
  5. from django.conf import settings
  6. from django.contrib.auth import forms as auth_forms
  7. from django.contrib.auth.forms import AuthenticationForm
  8. from django.contrib.sites.models import get_current_site
  9. from django.core.exceptions import ValidationError
  10. from django.utils.translation import ugettext_lazy as _
  11. from django.utils.translation import pgettext_lazy
  12. from oscar.core.loading import get_profile_class, get_class, get_model
  13. from oscar.core.compat import get_user_model, existing_user_fields
  14. from oscar.apps.customer.utils import get_password_reset_url, normalise_email
  15. from oscar.core.validators import password_validators
  16. Dispatcher = get_class('customer.utils', 'Dispatcher')
  17. CommunicationEventType = get_model('customer', 'communicationeventtype')
  18. ProductAlert = get_model('customer', 'ProductAlert')
  19. User = get_user_model()
  20. def generate_username():
  21. # Python 3 uses ascii_letters. If not available, fallback to letters
  22. try:
  23. letters = string.ascii_letters
  24. except AttributeError:
  25. letters = string.letters
  26. uname = ''.join([random.choice(letters + string.digits + '_')
  27. for i in range(30)])
  28. try:
  29. User.objects.get(username=uname)
  30. return generate_username()
  31. except User.DoesNotExist:
  32. return uname
  33. class PasswordResetForm(auth_forms.PasswordResetForm):
  34. """
  35. This form takes the same structure as its parent from django.contrib.auth
  36. """
  37. communication_type_code = "PASSWORD_RESET"
  38. def save(self, domain_override=None, use_https=False, request=None,
  39. **kwargs):
  40. """
  41. Generates a one-use only link for resetting password and sends to the
  42. user.
  43. """
  44. site = get_current_site(request)
  45. if domain_override is not None:
  46. site.domain = site.name = domain_override
  47. email = self.cleaned_data['email']
  48. active_users = User._default_manager.filter(
  49. email__iexact=email, is_active=True)
  50. for user in active_users:
  51. # Build reset url
  52. reset_url = "%s://%s%s" % (
  53. 'https' if use_https else 'http',
  54. site.domain,
  55. get_password_reset_url(user))
  56. ctx = {
  57. 'user': user,
  58. 'site': site,
  59. 'reset_url': reset_url}
  60. messages = CommunicationEventType.objects.get_and_render(
  61. code=self.communication_type_code, context=ctx)
  62. Dispatcher().dispatch_user_messages(user, messages)
  63. class SetPasswordForm(auth_forms.SetPasswordForm):
  64. def __init__(self, *args, **kwargs):
  65. super(SetPasswordForm, self).__init__(*args, **kwargs)
  66. # Enforce password validations for the new password
  67. self.fields['new_password1'].validators += password_validators
  68. class PasswordChangeForm(auth_forms.PasswordChangeForm):
  69. def __init__(self, *args, **kwargs):
  70. super(PasswordChangeForm, self).__init__(*args, **kwargs)
  71. # Enforce password validations for the new password
  72. self.fields['new_password1'].validators += password_validators
  73. class EmailAuthenticationForm(AuthenticationForm):
  74. """
  75. Extends the standard django AuthenticationForm, to support 75 character
  76. usernames. 75 character usernames are needed to support the EmailOrUsername
  77. auth backend.
  78. """
  79. username = forms.EmailField(label=_('Email address'))
  80. redirect_url = forms.CharField(
  81. widget=forms.HiddenInput, required=False)
  82. def __init__(self, host, *args, **kwargs):
  83. self.host = host
  84. super(EmailAuthenticationForm, self).__init__(*args, **kwargs)
  85. def clean_redirect_url(self):
  86. url = self.cleaned_data['redirect_url'].strip()
  87. if url:
  88. # Ensure URL is not to a different host
  89. host = parse.urlparse(url)[1]
  90. if host and host == self.host:
  91. return url
  92. class ConfirmPasswordForm(forms.Form):
  93. """
  94. Extends the standard django AuthenticationForm, to support 75 character
  95. usernames. 75 character usernames are needed to support the EmailOrUsername
  96. auth backend.
  97. """
  98. password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
  99. def __init__(self, user, *args, **kwargs):
  100. super(ConfirmPasswordForm, self).__init__(*args, **kwargs)
  101. self.user = user
  102. def clean_password(self):
  103. password = self.cleaned_data['password']
  104. if not self.user.check_password(password):
  105. raise forms.ValidationError(
  106. _("The entered password is not valid!"))
  107. return password
  108. class EmailUserCreationForm(forms.ModelForm):
  109. email = forms.EmailField(label=_('Email address'))
  110. password1 = forms.CharField(
  111. label=_('Password'), widget=forms.PasswordInput,
  112. validators=password_validators)
  113. password2 = forms.CharField(
  114. label=_('Confirm password'), widget=forms.PasswordInput)
  115. redirect_url = forms.CharField(
  116. widget=forms.HiddenInput, required=False)
  117. class Meta:
  118. model = User
  119. fields = ('email',)
  120. def __init__(self, host=None, *args, **kwargs):
  121. self.host = host
  122. super(EmailUserCreationForm, self).__init__(*args, **kwargs)
  123. def clean_email(self):
  124. email = normalise_email(self.cleaned_data['email'])
  125. if User._default_manager.filter(email=email).exists():
  126. raise forms.ValidationError(
  127. _("A user with that email address already exists"))
  128. return email
  129. def clean_password2(self):
  130. password1 = self.cleaned_data.get('password1', '')
  131. password2 = self.cleaned_data.get('password2', '')
  132. if password1 != password2:
  133. raise forms.ValidationError(
  134. _("The two password fields didn't match."))
  135. return password2
  136. def clean_redirect_url(self):
  137. url = self.cleaned_data['redirect_url'].strip()
  138. if not url:
  139. return settings.LOGIN_REDIRECT_URL
  140. host = parse.urlparse(url)[1]
  141. if host and self.host and host != self.host:
  142. return settings.LOGIN_REDIRECT_URL
  143. return url
  144. def save(self, commit=True):
  145. user = super(EmailUserCreationForm, self).save(commit=False)
  146. user.set_password(self.cleaned_data['password1'])
  147. if 'username' in [f.name for f in User._meta.fields]:
  148. user.username = generate_username()
  149. if commit:
  150. user.save()
  151. return user
  152. class OrderSearchForm(forms.Form):
  153. date_from = forms.DateField(
  154. required=False, label=pgettext_lazy("start date", "From"))
  155. date_to = forms.DateField(
  156. required=False, label=pgettext_lazy("end date", "To"))
  157. order_number = forms.CharField(required=False, label=_("Order number"))
  158. def clean(self):
  159. if self.is_valid() and not any([self.cleaned_data['date_from'],
  160. self.cleaned_data['date_to'],
  161. self.cleaned_data['order_number']]):
  162. raise forms.ValidationError(_("At least one field is required."))
  163. return super(OrderSearchForm, self).clean()
  164. def description(self):
  165. """
  166. Uses the form's data to build a useful description of what orders
  167. are listed.
  168. """
  169. if not self.is_bound or not self.is_valid():
  170. return _('All orders')
  171. else:
  172. date_from = self.cleaned_data['date_from']
  173. date_to = self.cleaned_data['date_to']
  174. order_number = self.cleaned_data['order_number']
  175. return self._orders_description(date_from, date_to, order_number)
  176. def _orders_description(self, date_from, date_to, order_number):
  177. if date_from and date_to:
  178. if order_number:
  179. desc = _('Orders placed between %(date_from)s and '
  180. '%(date_to)s and order number containing '
  181. '%(order_number)s')
  182. else:
  183. desc = _('Orders placed between %(date_from)s and '
  184. '%(date_to)s')
  185. elif date_from:
  186. if order_number:
  187. desc = _('Orders placed since %(date_from)s and '
  188. 'order number containing %(order_number)s')
  189. else:
  190. desc = _('Orders placed since %(date_from)s')
  191. elif date_to:
  192. if order_number:
  193. desc = _('Orders placed until %(date_to)s and '
  194. 'order number containing %(order_number)s')
  195. else:
  196. desc = _('Orders placed until %(date_to)s')
  197. elif order_number:
  198. desc = _('Orders with order number containing %(order_number)s')
  199. else:
  200. return None
  201. params = {
  202. 'date_from': date_from,
  203. 'date_to': date_to,
  204. 'order_number': order_number,
  205. }
  206. return desc % params
  207. def get_filters(self):
  208. date_from = self.cleaned_data['date_from']
  209. date_to = self.cleaned_data['date_to']
  210. order_number = self.cleaned_data['order_number']
  211. kwargs = {}
  212. if date_from and date_to:
  213. kwargs['date_placed__range'] = [date_from, date_to]
  214. elif date_from and not date_to:
  215. kwargs['date_placed__gt'] = date_from
  216. elif not date_from and date_to:
  217. kwargs['date_placed__lt'] = date_to
  218. if order_number:
  219. kwargs['number__contains'] = order_number
  220. return kwargs
  221. class UserForm(forms.ModelForm):
  222. def __init__(self, user, *args, **kwargs):
  223. self.user = user
  224. kwargs['instance'] = user
  225. super(UserForm, self).__init__(*args, **kwargs)
  226. if 'email' in self.fields:
  227. self.fields['email'].required = True
  228. def clean_email(self):
  229. """
  230. Make sure that the email address is aways unique as it is
  231. used instead of the username. This is necessary because the
  232. unique-ness of email addresses is *not* enforced on the model
  233. level in ``django.contrib.auth.models.User``.
  234. """
  235. email = normalise_email(self.cleaned_data['email'])
  236. if User._default_manager.filter(
  237. email=email).exclude(id=self.user.id).exists():
  238. raise ValidationError(
  239. _("A user with this email address already exists"))
  240. return email
  241. class Meta:
  242. model = User
  243. fields = existing_user_fields(['first_name', 'last_name', 'email'])
  244. Profile = get_profile_class()
  245. if Profile:
  246. class UserAndProfileForm(forms.ModelForm):
  247. def __init__(self, user, *args, **kwargs):
  248. try:
  249. instance = Profile.objects.get(user=user)
  250. except Profile.DoesNotExist:
  251. # User has no profile, try a blank one
  252. instance = Profile(user=user)
  253. kwargs['instance'] = instance
  254. super(UserAndProfileForm, self).__init__(*args, **kwargs)
  255. # Get profile field names to help with ordering later
  256. profile_field_names = self.fields.keys()
  257. # Get user field names (we look for core user fields first)
  258. core_field_names = set([f.name for f in User._meta.fields])
  259. user_field_names = ['email']
  260. for field_name in ('first_name', 'last_name'):
  261. if field_name in core_field_names:
  262. user_field_names.append(field_name)
  263. user_field_names.extend(User._meta.additional_fields)
  264. # Store user fields so we know what to save later
  265. self.user_field_names = user_field_names
  266. # Add additional user form fields
  267. additional_fields = forms.fields_for_model(
  268. User, fields=user_field_names)
  269. self.fields.update(additional_fields)
  270. # Ensure email is required and initialised correctly
  271. self.fields['email'].required = True
  272. # Set initial values
  273. for field_name in user_field_names:
  274. self.fields[field_name].initial = getattr(user, field_name)
  275. # Ensure order of fields is email, user fields then profile fields
  276. self.fields.keyOrder = user_field_names + profile_field_names
  277. class Meta:
  278. model = Profile
  279. exclude = ('user',)
  280. def clean_email(self):
  281. email = normalise_email(self.cleaned_data['email'])
  282. if User._default_manager.filter(
  283. email=email).exclude(id=self.instance.user.id).exists():
  284. raise ValidationError(
  285. _("A user with this email address already exists"))
  286. return email
  287. def save(self, *args, **kwargs):
  288. user = self.instance.user
  289. # Save user also
  290. for field_name in self.user_field_names:
  291. setattr(user, field_name, self.cleaned_data[field_name])
  292. user.save()
  293. return super(ProfileForm, self).save(*args, **kwargs)
  294. ProfileForm = UserAndProfileForm
  295. else:
  296. ProfileForm = UserForm
  297. class ProductAlertForm(forms.ModelForm):
  298. email = forms.EmailField(required=True, label=_(u'Send notification to'),
  299. widget=forms.TextInput(attrs={
  300. 'placeholder': _('Enter your email')
  301. }))
  302. def __init__(self, user, product, *args, **kwargs):
  303. self.user = user
  304. self.product = product
  305. super(ProductAlertForm, self).__init__(*args, **kwargs)
  306. # Only show email field to unauthenticated users
  307. if user and user.is_authenticated():
  308. self.fields['email'].widget = forms.HiddenInput()
  309. self.fields['email'].required = False
  310. def save(self, commit=True):
  311. alert = super(ProductAlertForm, self).save(commit=False)
  312. if self.user.is_authenticated():
  313. alert.user = self.user
  314. alert.product = self.product
  315. if commit:
  316. alert.save()
  317. return alert
  318. def clean(self):
  319. cleaned_data = self.cleaned_data
  320. email = cleaned_data.get('email')
  321. if email:
  322. try:
  323. ProductAlert.objects.get(
  324. product=self.product, email=email,
  325. status=ProductAlert.ACTIVE)
  326. except ProductAlert.DoesNotExist:
  327. pass
  328. else:
  329. raise forms.ValidationError(_(
  330. "There is already an active stock alert for %s") % email)
  331. elif self.user.is_authenticated():
  332. try:
  333. ProductAlert.objects.get(product=self.product,
  334. user=self.user,
  335. status=ProductAlert.ACTIVE)
  336. except ProductAlert.DoesNotExist:
  337. pass
  338. else:
  339. raise forms.ValidationError(_(
  340. "You already have an active alert for this product"))
  341. return cleaned_data
  342. class Meta:
  343. model = ProductAlert
  344. exclude = ('user', 'key',
  345. 'status', 'date_confirmed', 'date_cancelled', 'date_closed',
  346. 'product')