Coverage for ivatar/ivataraccount/forms.py: 100%

80 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-26 00:11 +0000

1# -*- coding: utf-8 -*- 

2""" 

3Classes for our ivatar.ivataraccount.forms 

4""" 

5from urllib.parse import urlsplit, urlunsplit 

6 

7from django import forms 

8from django.utils.translation import gettext_lazy as _ 

9 

10from ipware import get_client_ip 

11 

12from ivatar import settings 

13from ivatar.settings import MIN_LENGTH_EMAIL, MAX_LENGTH_EMAIL 

14from ivatar.settings import MIN_LENGTH_URL, MAX_LENGTH_URL 

15from .models import UnconfirmedEmail, ConfirmedEmail, Photo 

16from .models import UnconfirmedOpenId, ConfirmedOpenId 

17from .models import UserPreference 

18 

19 

20MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5 

21 

22 

23class AddEmailForm(forms.Form): 

24 """ 

25 Form to handle adding email addresses 

26 """ 

27 

28 email = forms.EmailField( 

29 label=_("Email"), 

30 min_length=MIN_LENGTH_EMAIL, 

31 max_length=MAX_LENGTH_EMAIL, 

32 ) 

33 

34 def clean_email(self): 

35 """ 

36 Enforce lowercase email 

37 """ 

38 # TODO: Domain restriction as in libravatar? 

39 return self.cleaned_data["email"].lower() 

40 

41 def save(self, request): 

42 """ 

43 Save the model, ensuring some safety 

44 """ 

45 user = request.user 

46 # Enforce the maximum number of unconfirmed emails a user can have 

47 num_unconfirmed = user.unconfirmedemail_set.count() 

48 

49 max_num_unconfirmed_emails = getattr( 

50 settings, "MAX_NUM_UNCONFIRMED_EMAILS", MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT 

51 ) 

52 

53 if num_unconfirmed >= max_num_unconfirmed_emails: 

54 self.add_error(None, _("Too many unconfirmed mail addresses!")) 

55 return False 

56 

57 # Check whether or not a confirmation email has been 

58 # sent by this user already 

59 if UnconfirmedEmail.objects.filter( # pylint: disable=no-member 

60 user=user, email=self.cleaned_data["email"] 

61 ).exists(): 

62 self.add_error("email", _("Address already added, currently unconfirmed")) 

63 return False 

64 

65 # Check whether or not the email is already confirmed (by someone) 

66 check_mail = ConfirmedEmail.objects.filter(email=self.cleaned_data["email"]) 

67 if check_mail.exists(): 

68 msg = _("Address already confirmed (by someone else)") 

69 if check_mail.first().user == request.user: 

70 msg = _("Address already confirmed (by you)") 

71 self.add_error("email", msg) 

72 return False 

73 

74 unconfirmed = UnconfirmedEmail() 

75 unconfirmed.email = self.cleaned_data["email"] 

76 unconfirmed.user = user 

77 unconfirmed.save() 

78 unconfirmed.send_confirmation_mail(url=request.build_absolute_uri("/")[:-1]) 

79 return True 

80 

81 

82class UploadPhotoForm(forms.Form): 

83 """ 

84 Form handling photo upload 

85 """ 

86 

87 photo = forms.FileField( 

88 label=_("Photo"), 

89 error_messages={"required": _("You must choose an image to upload.")}, 

90 ) 

91 not_porn = forms.BooleanField( 

92 label=_("suitable for all ages (i.e. no offensive content)"), 

93 required=True, 

94 error_messages={ 

95 "required": _( 

96 'We only host "G-rated" images and so this field must be checked.' 

97 ) 

98 }, 

99 ) 

100 can_distribute = forms.BooleanField( 

101 label=_("can be freely copied"), 

102 required=True, 

103 error_messages={ 

104 "required": _( 

105 "This field must be checked since we need to be able to distribute photos to third parties." 

106 ) 

107 }, 

108 ) 

109 

110 @staticmethod 

111 def save(request, data): 

112 """ 

113 Save the model and assign it to the current user 

114 """ 

115 # Link this file to the user's profile 

116 photo = Photo() 

117 photo.user = request.user 

118 photo.ip_address = get_client_ip(request)[0] 

119 photo.data = data.read() 

120 photo.save() 

121 if not photo.pk: 

122 return None 

123 return photo 

124 

125 

126class AddOpenIDForm(forms.Form): 

127 """ 

128 Form to handle adding OpenID 

129 """ 

130 

131 openid = forms.URLField( 

132 label=_("OpenID"), 

133 min_length=MIN_LENGTH_URL, 

134 max_length=MAX_LENGTH_URL, 

135 initial="http://", 

136 ) 

137 

138 def clean_openid(self): 

139 """ 

140 Enforce restrictions 

141 """ 

142 # Lowercase hostname port of the URL 

143 url = urlsplit(self.cleaned_data["openid"]) 

144 data = urlunsplit( 

145 (url.scheme.lower(), url.netloc.lower(), url.path, url.query, url.fragment) 

146 ) 

147 

148 # TODO: Domain restriction as in libravatar? 

149 return data 

150 

151 def save(self, user): 

152 """ 

153 Save the model, ensuring some safety 

154 """ 

155 if ConfirmedOpenId.objects.filter( # pylint: disable=no-member 

156 openid=self.cleaned_data["openid"] 

157 ).exists(): 

158 self.add_error("openid", _("OpenID already added and confirmed!")) 

159 return False 

160 

161 if UnconfirmedOpenId.objects.filter( # pylint: disable=no-member 

162 openid=self.cleaned_data["openid"] 

163 ).exists(): 

164 self.add_error("openid", _("OpenID already added, but not confirmed yet!")) 

165 return False 

166 

167 unconfirmed = UnconfirmedOpenId() 

168 unconfirmed.openid = self.cleaned_data["openid"] 

169 unconfirmed.user = user 

170 unconfirmed.save() 

171 

172 return unconfirmed.pk 

173 

174 

175class UpdatePreferenceForm(forms.ModelForm): 

176 """ 

177 Form for updating user preferences 

178 """ 

179 

180 class Meta: # pylint: disable=too-few-public-methods 

181 """ 

182 Meta class for UpdatePreferenceForm 

183 """ 

184 

185 model = UserPreference 

186 fields = ["theme"] 

187 

188 

189class UploadLibravatarExportForm(forms.Form): 

190 """ 

191 Form handling libravatar user export upload 

192 """ 

193 

194 export_file = forms.FileField( 

195 label=_("Export file"), 

196 error_messages={"required": _("You must choose an export file to upload.")}, 

197 ) 

198 not_porn = forms.BooleanField( 

199 label=_("suitable for all ages (i.e. no offensive content)"), 

200 required=True, 

201 error_messages={ 

202 "required": _( 

203 'We only host "G-rated" images and so this field must be checked.' 

204 ) 

205 }, 

206 ) 

207 can_distribute = forms.BooleanField( 

208 label=_("can be freely copied"), 

209 required=True, 

210 error_messages={ 

211 "required": _( 

212 "This field must be checked since we need to be able to\ 

213 distribute photos to third parties." 

214 ) 

215 }, 

216 ) 

217 

218 

219class DeleteAccountForm(forms.Form): 

220 password = forms.CharField( 

221 label=_("Password"), required=False, widget=forms.PasswordInput() 

222 )