Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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