Coverage for ivatar/ivataraccount/forms.py: 100%
77 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-12 23:12 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-12 23:12 +0000
1# -*- coding: utf-8 -*-
2"""
3Classes for our ivatar.ivataraccount.forms
4"""
5from urllib.parse import urlsplit, urlunsplit
7from django import forms
8from django.utils.translation import gettext_lazy as _
10from ipware import get_client_ip
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
20MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
23class AddEmailForm(forms.Form):
24 """
25 Form to handle adding email addresses
26 """
28 email = forms.EmailField(
29 label=_("Email"),
30 min_length=MIN_LENGTH_EMAIL,
31 max_length=MAX_LENGTH_EMAIL,
32 )
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()
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()
49 max_num_unconfirmed_emails = getattr(
50 settings, "MAX_NUM_UNCONFIRMED_EMAILS", MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT
51 )
53 if num_unconfirmed >= max_num_unconfirmed_emails:
54 self.add_error(None, _("Too many unconfirmed mail addresses!"))
55 return False
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
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
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
82class UploadPhotoForm(forms.Form):
83 """
84 Form handling photo upload
85 """
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 )
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 return photo if photo.pk else None
124class AddOpenIDForm(forms.Form):
125 """
126 Form to handle adding OpenID
127 """
129 openid = forms.URLField(
130 label=_("OpenID"),
131 min_length=MIN_LENGTH_URL,
132 max_length=MAX_LENGTH_URL,
133 initial="http://",
134 )
136 def clean_openid(self):
137 """
138 Enforce restrictions
139 """
140 # Lowercase hostname port of the URL
141 url = urlsplit(self.cleaned_data["openid"])
142 return urlunsplit(
143 (
144 url.scheme.lower(),
145 url.netloc.lower(),
146 url.path,
147 url.query,
148 url.fragment,
149 )
150 )
152 def save(self, user):
153 """
154 Save the model, ensuring some safety
155 """
156 if ConfirmedOpenId.objects.filter( # pylint: disable=no-member
157 openid=self.cleaned_data["openid"]
158 ).exists():
159 self.add_error("openid", _("OpenID already added and confirmed!"))
160 return False
162 if UnconfirmedOpenId.objects.filter( # pylint: disable=no-member
163 openid=self.cleaned_data["openid"]
164 ).exists():
165 self.add_error("openid", _("OpenID already added, but not confirmed yet!"))
166 return False
168 unconfirmed = UnconfirmedOpenId()
169 unconfirmed.openid = self.cleaned_data["openid"]
170 unconfirmed.user = user
171 unconfirmed.save()
173 return unconfirmed.pk
176class UpdatePreferenceForm(forms.ModelForm):
177 """
178 Form for updating user preferences
179 """
181 class Meta: # pylint: disable=too-few-public-methods
182 """
183 Meta class for UpdatePreferenceForm
184 """
186 model = UserPreference
187 fields = ["theme"]
190class UploadLibravatarExportForm(forms.Form):
191 """
192 Form handling libravatar user export upload
193 """
195 export_file = forms.FileField(
196 label=_("Export file"),
197 error_messages={"required": _("You must choose an export file to upload.")},
198 )
199 not_porn = forms.BooleanField(
200 label=_("suitable for all ages (i.e. no offensive content)"),
201 required=True,
202 error_messages={
203 "required": _(
204 'We only host "G-rated" images and so this field must be checked.'
205 )
206 },
207 )
208 can_distribute = forms.BooleanField(
209 label=_("can be freely copied"),
210 required=True,
211 error_messages={
212 "required": _(
213 "This field must be checked since we need to be able to\
214 distribute photos to third parties."
215 )
216 },
217 )
220class DeleteAccountForm(forms.Form):
221 password = forms.CharField(
222 label=_("Password"), required=False, widget=forms.PasswordInput()
223 )