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 16KB

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