1 # -*- coding: utf-8 -*-
3 # portal/forms.py: forms for the portal application
4 # This file is part of the Manifold project.
7 # Jordan Augé <jordan.auge@lip6.fr>
8 # Mohammed-Yasin Rahman <mohammed-yasin.rahman@lip6.fr>
9 # Copyright 2013, UPMC Sorbonne Universités / LIP6
11 # This program is free software; you can redistribute it and/or modify it under
12 # the terms of the GNU General Public License as published by the Free Software
13 # Foundation; either version 3, or (at your option) any later version.
15 # This program is distributed in the hope that it will be useful, but WITHOUT
16 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20 # You should have received a copy of the GNU General Public License along with
21 # this program; see the file COPYING. If not, write to the Free Software
22 # Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 from __future__ import print_function
26 from django import forms
27 from portal.models import PendingUser, PendingSlice
28 #from crispy_forms.helper import FormHelper
29 #from crispy_forms.layout import Submit
30 from django.utils.translation import ugettext_lazy as _
31 from django.contrib.auth.tokens import default_token_generator
32 from django.contrib.auth import authenticate, get_user_model
33 from django.contrib.sites.models import get_current_site
34 from django.utils.http import int_to_base36
35 from django.template import loader
37 # TODO: Remove these automated forms and use html templates and views like any other page !
38 from django.contrib.auth.hashers import identify_hasher
39 # adapted from https://sourcegraph.com/github.com/fusionbox/django-authtools/symbols/python/authtools/forms
41 def is_password_unusable(pw):
42 # like Django's is_password_usable, but only checks for unusable
43 # passwords, not invalidly encoded passwords too.
46 from django.contrib.auth.hashers import UNUSABLE_PASSWORD
47 return pw == UNUSABLE_PASSWORD
50 from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX
51 return pw.startswith(UNUSABLE_PASSWORD_PREFIX)
57 # bootstrap3 requires the <input> fields to be tagged class='form-control'
58 # my first idea was to add this in the view template of course, BUT
59 # I can't find a way to access the 'type=' value for a given field
60 # I've looked rather deeply out there but to no avail so far
61 # so as we have a demo coming up soon, and until we can come with a less intrusive way to handle this...
64 #class ContactForm(forms.Form):
65 # first_name = forms.CharField()
66 # last_name = forms.CharField()
67 # affiliation = forms.CharField()
68 # subject = forms.CharField(max_length=100)
69 # message = forms.CharField(widget=forms.Textarea)
70 # email = forms.EmailField()
71 # cc_myself = forms.BooleanField(required=False)
73 class ContactForm(forms.Form):
74 # first_name = forms.RegexField(widget=forms.TextInput(attrs={'class':'form-control'}),
75 # regex=r'^[\w.@+-]+$',
77 # label=_("First name"),
78 # error_messages={'invalid': _("This value may contain only letters, numbers and @/./+/-/_ characters.")})
79 # last_name = forms.RegexField(widget=forms.TextInput(attrs={'class':'form-control'}),
80 # regex=r'^[\w.@+-]+$',
82 # label=_("Last name"),
83 # error_messages={'invalid': _("This value may contain only letters, numbers and @/./+/-/_ characters.")})
84 # authority = forms.RegexField(widget=forms.TextInput(attrs={'class':'form-control'}),
85 # regex=r'^[\w.@+-]+$',
87 # label=_("authority"),
88 # error_messages={'invalid': _("This value may contain only letters, numbers and @/./+/-/_ characters.")})
89 email = forms.EmailField(widget=forms.TextInput(attrs={'class':'form-control'}))
90 subject = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
91 description = forms.CharField(widget=forms.Textarea(attrs={'class':'form-control'}))
92 #cc_myself = forms.BooleanField(required=False,widget=forms.CheckboxInput(attrs={'class':'form-control'}))
94 class PassResetForm(forms.Form):
95 email = forms.EmailField(widget=forms.TextInput(attrs={'class':'form-control'}))
97 #class SliceRequestForm(forms.Form):
98 # slice_name = forms.CharField()
99 # authority_hrn = forms.ChoiceField(choices=[(1, 'un')])
100 # number_of_nodes = forms.DecimalField()
101 # type_of_nodes = forms.CharField()
102 # purpose = forms.CharField(widget=forms.Textarea)
103 # email = forms.EmailField()
104 # cc_myself = forms.BooleanField(required=False)
106 # slice_name = forms.CharField(
107 # widget=forms.TextInput(attrs={'class':'form-control'}),
108 # help_text="The name for the slice you wish to create")
109 # authority_hrn = forms.ChoiceField(
110 # widget = forms.Select(attrs={'class':'form-control'}),
112 # help_text = "An authority responsible for vetting your slice")
113 # number_of_nodes = forms.DecimalField(
114 # widget = forms.TextInput(attrs={'class':'form-control'}),
115 # help_text = "The number of nodes you expect to request (informative)")
116 # type_of_nodes = forms.CharField(
117 # widget = forms.TextInput(attrs={'class':'form-control'}),
118 # help_text = "The type of nodes you expect to request (informative)")
119 # purpose = forms.CharField(
120 # widget = forms.Textarea(attrs={'class':'form-control'}),
121 # help_text = "The purpose of your experiment (informative)")
122 # email = forms.EmailField(
123 # widget = forms.TextInput(attrs={'class':'form-control'}),
124 # help_text = "Your email address")
125 # cc_myself = forms.BooleanField(
126 # widget = forms.CheckboxInput(attrs={'class':'form-control'}),
128 # help_text = "If you'd like to be cc'ed on the request email")
130 # def __init__(self, *args, **kwargs):
131 # initial = kwargs.get('initial', {})
132 # authority_hrn = initial.get('authority_hrn', None)
134 # # set just the initial value
135 # # in the real form needs something like this {'authority_hrn':'a'}
136 # # but in this case you want {'authority_hrn':('a', 'letter_a')}
138 # kwargs['initial']['authority_hrn'] = authority_hrn[0]
141 # super(SliceRequestForm, self).__init__(*args, **kwargs)
143 # # self.fields only exist after, so a double validation is needed
144 # if authority_hrn:# and authority_hrn[0] not in (c[0] for c in authority_hrn):
145 # # XXX This does not work, the choicefield is not updated...
146 # #self.fields['authority_hrn'].choices.extend(authority_hrn)
147 # self.fields['authority_hrn'] = forms.ChoiceField(
148 # widget = forms.Select(attrs={'class':'form-control'}),
149 # choices = authority_hrn,
150 # help_text = "An authority responsible for vetting your slice")
153 class PasswordResetForm(forms.Form):
155 'unknown': _("That email address doesn't have an associated "
156 "user account. Are you sure you've registered?"),
157 'unusable': _("The user account associated with this email "
158 "address cannot reset the password."),
160 email = forms.EmailField(label=_("Email"), max_length=254)
162 def clean_email(self):
164 Validates that an active user exists with the given email address.
166 UserModel = get_user_model()
167 email = self.cleaned_data["email"]
168 self.users_cache = UserModel._default_manager.filter(email__iexact=email)
169 if not len(self.users_cache):
170 raise forms.ValidationError(self.error_messages['unknown'])
171 if not any(user.is_active for user in self.users_cache):
172 # none of the filtered users are active
173 raise forms.ValidationError(self.error_messages['unknown'])
174 if any(is_password_unusable(user.password) for user in self.users_cache):
175 raise forms.ValidationError(self.error_messages['unusable'])
178 def save(self, domain_override=None,
179 subject_template_name='registration/password_reset_subject.txt',
180 email_template_name='registration/password_reset_email.html',
181 use_https=False, token_generator=default_token_generator,
182 from_email=None, request=None):
184 Generates a one-use only link for resetting password and sends to the
187 from django.core.mail import send_mail,EmailMultiAlternatives
189 for user in self.users_cache:
190 if not domain_override:
191 current_site = get_current_site(request)
192 site_name = current_site.name
193 domain = current_site.domain
195 site_name = domain = domain_override
199 'site_name': site_name,
200 'uid': int_to_base36(user.pk),
202 'token': token_generator.make_token(user),
203 'protocol': use_https and 'https' or 'http',
205 subject = loader.render_to_string(subject_template_name, c)
206 # Email subject *must not* contain newlines
207 subject = ''.join(subject.splitlines())
208 email = loader.render_to_string(email_template_name, c)
209 send_mail(subject, email, from_email, [user.email])
211 print("Failed to send email, please check the mail templates and the SMTP configuration of your server")
214 class SetPasswordForm(forms.Form):
216 A form that lets a user change set his/her password without entering the
220 'password_mismatch': _("The two password fields didn't match."),
222 new_password1 = forms.CharField(label=_("New password"),
223 widget=forms.PasswordInput)
224 new_password2 = forms.CharField(label=_("New password confirmation"),
225 widget=forms.PasswordInput)
227 def __init__(self, user, *args, **kwargs):
229 super(SetPasswordForm, self).__init__(*args, **kwargs)
231 def clean_new_password2(self):
232 password1 = self.cleaned_data.get('new_password1')
233 password2 = self.cleaned_data.get('new_password2')
234 if password1 and password2:
235 if password1 != password2:
236 raise forms.ValidationError(
237 self.error_messages['password_mismatch'])
240 def save(self, commit=True):
241 self.user.set_password(self.cleaned_data['new_password1'])