• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

liqd / adhocracy-plus / 28178848451

25 Jun 2026 02:50PM UTC coverage: 41.038% (-45.5%) from 86.506%
28178848451

Pull #3127

github

web-flow
Merge 45990f4e4 into 28ae9021c
Pull Request #3127: app/projects: New Styling for Module Page

3439 of 8380 relevant lines covered (41.04%)

0.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

29.94
/apps/users/forms.py
1
import logging
1✔
2

3
import xmltodict
1✔
4
from allauth.account.forms import LoginForm
1✔
5
from allauth.account.forms import SignupForm
1✔
6
from allauth.socialaccount.forms import SignupForm as SocialSignupForm
1✔
7
from django import forms
1✔
8
from django.conf import settings
1✔
9
from django.contrib.auth import forms as auth_forms
1✔
10
from django.utils.translation import get_language
1✔
11
from django.utils.translation import gettext_lazy as _
1✔
12
from zeep import Client
1✔
13

14
from apps.captcha.fields import ProsopoCaptchaField
1✔
15
from apps.cms.settings import helpers
1✔
16
from apps.organisations.models import Member
1✔
17
from apps.organisations.models import Organisation
1✔
18
from apps.users.models import User
1✔
19

20
logger = logging.getLogger(__name__)
1✔
21

22
PROSOPO_CAPTCHA_HELP = _(
1✔
23
    "Please complete the captcha verification.<strong>"
24
    "If you are having difficulty please contact us by {}email{}.</strong>"
25
)
26

27

28
class BotTrapMixin:
1✔
29
    def __init__(self, *args, **kwargs):
1✔
30
        super().__init__(*args, **kwargs)
×
31
        self.fields["accept_marketing_partners"] = forms.BooleanField(
×
32
            label=_("Accept marketing from our partners"),
33
            required=False,
34
            widget=forms.CheckboxInput(attrs={"autocomplete": "off"}),
35
        )
36

37
    def apply_bot_trap(self, user):
1✔
38
        if user and self.cleaned_data.get("accept_marketing_partners"):
×
39
            user.is_active = False
×
40
        return user
×
41

42

43
class TermsAndCaptchaMixin:
1✔
44
    def __init__(self, *args, **kwargs):
1✔
45
        super().__init__(*args, **kwargs)
×
46

47
        if "terms_of_use" not in self.fields:
×
48
            self.fields["terms_of_use"] = forms.BooleanField(label=_("Terms of use"))
×
49

50
        if not getattr(settings, "CAPTCHA", False):
×
51
            return  # Don't add captcha field
×
52

53
        try:
×
54
            self.fields["captcha"] = ProsopoCaptchaField(label=_("I am not a robot"))
×
55
            self.fields["captcha"].help_text = helpers.add_email_link_to_helptext(
×
56
                self.fields["captcha"].help_text, PROSOPO_CAPTCHA_HELP
57
            )
58
        except Exception as e:
×
59
            logger.warning(
×
60
                f"Could not add Prosopo captcha field, are the PROSOPO_SITE_KEY and PROSOPO_SECRET_KEY set : {e}"
61
            )
62

63

64
class DefaultLoginForm(LoginForm):
1✔
65
    def __init__(self, *args, **kwargs):
1✔
66
        super().__init__(*args, **kwargs)
×
67
        self.fields["login"].label = _("Username/e-mail")
×
68
        del self.fields["login"].widget.attrs["placeholder"]
×
69
        del self.fields["password"].widget.attrs["placeholder"]
×
70
        self.fields["password"].help_text = ""
×
71
        self.fields["login"].widget.attrs["autocomplete"] = "username"
×
72
        self.fields["password"].widget.attrs["autocomplete"] = "current-password"
×
73
        self.fields["password"].widget.attrs["class"] = "password-toggle"
×
74

75
class DefaultSignupForm(BotTrapMixin, TermsAndCaptchaMixin, SignupForm):
1✔
76
    terms_of_use = forms.BooleanField(label=_("Terms of use"))
1✔
77
    get_newsletters = forms.BooleanField(
1✔
78
        label=_("I would like to receive further information"),
79
        help_text=_(
80
            "Projects you are following can send you "
81
            "additional information via email."
82
        ),
83
        required=False,
84
    )
85

86
    def __init__(self, *args, **kwargs):
1✔
87
        super().__init__(*args, **kwargs)
×
88

89
        self.fields["username"].help_text = _(
×
90
            "Your username will appear publicly next to your posts."
91
        )
92
        self.fields["email"].widget.attrs["autofocus"] = True
×
93
        del self.fields["username"].widget.attrs["placeholder"]
×
94
        del self.fields["email"].widget.attrs["placeholder"]
×
95
        del self.fields["password1"].widget.attrs["placeholder"]
×
96
        del self.fields["password2"].widget.attrs["placeholder"]
×
97
        self.fields["email"].widget.attrs["autocomplete"] = "username"
×
98
        self.fields["password1"].widget.attrs["autocomplete"] = "new-password"
×
99
        self.fields["password2"].widget.attrs["autocomplete"] = "new-password"
×
100
        self.fields["password1"].widget.attrs["class"] = "password-toggle"
×
101
        self.fields["password2"].widget.attrs["class"] = "password-toggle"
×
102

103
    def save(self, request):
1✔
104
        user = super().save(request)
×
105
        if user:
×
106
            user.get_newsletters = self.cleaned_data["get_newsletters"]
×
107
            user.language = get_language()
×
108
            self.apply_bot_trap(user)
×
109
            user.save()
×
110
            return user
×
111

112

113
class GuestCreateForm(TermsAndCaptchaMixin, forms.Form):
1✔
114
    pass
1✔
115

116

117
class GuestConvertForm(DefaultSignupForm):
1✔
118

119
    get_newsletters = forms.BooleanField(
1✔
120
        label=_("I would like to receive further information"),
121
        help_text=_(
122
            "Projects you are following can send you "
123
            "additional information via email."
124
        ),
125
        required=False,
126
    )
127

128
    def __init__(self, *args, **kwargs):
1✔
129
        self.user = kwargs.pop("user", None)
×
130
        super().__init__(*args, **kwargs)
×
131

132
        if "captcha" in self.fields:
×
133
            del self.fields["captcha"]
×
134

135
        self.fields["email"].required = True
×
136
        self.fields["username"].required = True
×
137
        self.fields["password1"].required = True
×
138
        self.fields["password2"].required = True
×
139

140
    def clean_email(self):
1✔
141
        email = self.cleaned_data["email"]
×
142
        users = User.objects.filter(email__iexact=email)
×
143
        if users.exists():
×
144
            raise forms.ValidationError("Email already in use.")
×
145
        return email
×
146

147
    def save(self, request):
1✔
148
        user = self.user
×
149
        user.email = self.cleaned_data["email"]
×
150
        user.username = self.cleaned_data["username"]
×
151
        user.get_newsletters = self.cleaned_data["get_newsletters"]
×
152
        user.set_password(self.cleaned_data["password1"])
×
153
        user.save()
×
154

155
        return user
×
156

157

158
class IgbceSignupForm(DefaultSignupForm):
1✔
159
    member_number = forms.IntegerField(
1✔
160
        label=_("Membership number of IG BCE"),
161
        help_text=_(
162
            "The membership number consists of a seven-digit number "
163
            "and can be found on the membership card."
164
        ),
165
        max_value=99999999999999999999,
166
        min_value=0,
167
    )
168
    birth_date = forms.DateField(
1✔
169
        label=_("Date of birth"),
170
        help_text=_(
171
            "Please also enter your date of birth in the format "
172
            "MM/DD/YYYY for authentication. Only members of the "
173
            "IG BCE can participate."
174
        ),
175
    )
176
    terms_of_use_extra = forms.BooleanField(
1✔
177
        label=_(
178
            "I confirm that I have read and accepted the "
179
            '<a href="/info/ig-bce-datenschutz/" '
180
            'target="_blank">data protection policy</a> of IG '
181
            "BCE."
182
        )
183
    )
184

185
    def validateMemberNumberAndDate(self, member_number, birth_date):
1✔
186
        if not hasattr(settings, "IGBCE_NAV_URL") or not hasattr(
×
187
            settings, "IGBCE_NAV_SECURITYID"
188
        ):
189
            raise forms.ValidationError(
×
190
                _("Something is wrong with the setup - please try again later")
191
            )
192

193
        if Member.objects.filter(member_number=member_number).exists():
×
194
            raise forms.ValidationError(
×
195
                _(
196
                    "There is already a participant with this membership "
197
                    "number. Please check your entry. If this is your "
198
                    "membership number, please send an email to "
199
                    '"zukunftsgewerkschaft@igbce.de".'
200
                )
201
            )
202

203
        result = False
×
204
        try:
×
205
            client = Client("{}".format(settings.IGBCE_NAV_URL))
×
206
            parameters = "{};{}".format(member_number, birth_date.strftime("%d.%m.%Y"))
×
207
            connection_parameters = (
×
208
                "ObjectID:0;SecurityID:{};SetSize:0;UnitopProxyVersion:2.0".format(
209
                    settings.IGBCE_NAV_SECURITYID
210
                )
211
            )
212

213
            response = client.service.SendRequest(
×
214
                functionName="CALCONNECT_MemberNoBirthDate",
215
                functionParameters=parameters,
216
                filters="",
217
                connectionParameters=connection_parameters,
218
            )
219

220
            result_str = xmltodict.parse(response)["Response"]["ResponseData"][
×
221
                "Object"
222
            ]["CalConnectorResult"]
223

224
            if result_str == "true":
×
225
                result = True
×
226

227
        except BaseException:
×
228
            logger.exception("IGBCE API error")
×
229
            raise forms.ValidationError(
×
230
                _("Something is wrong with the setup - please try again later")
231
            )
232

233
        if not result:
×
234
            raise forms.ValidationError(
×
235
                _(
236
                    "Unfortunately, the member number and / or date of birth "
237
                    "could not be linked to an active member account. Please "
238
                    "check your input and try again. If you still have "
239
                    'problems, please contact "zukunftsgewerkschaft@igbce.de".'
240
                )
241
            )
242

243
    def clean(self):
1✔
244
        super().clean()
×
245

246
        if any(self.errors):
×
247
            return self.errors
×
248

249
        member_number = self.cleaned_data.get("member_number")
×
250
        birth_date = self.cleaned_data.get("birth_date")
×
251

252
        self.validateMemberNumberAndDate(member_number, birth_date)
×
253

254
    def save(self, request):
1✔
255
        user = super().save(request)
×
256
        if hasattr(settings, "SITE_ORGANISATION_SLUG"):
×
257
            organisation = Organisation.objects.get(
×
258
                slug=settings.SITE_ORGANISATION_SLUG
259
            )
260
            additional_info = {"birth_date": self.cleaned_data["birth_date"]}
×
261
            member = Member(
×
262
                member=user,
263
                member_number=self.cleaned_data["member_number"],
264
                organisation=organisation,
265
                additional_info=additional_info,
266
            )
267
            member.save()
×
268
        return user
×
269

270

271
class SocialTermsSignupForm(TermsAndCaptchaMixin, SocialSignupForm):
1✔
272
    get_newsletters = forms.BooleanField(
1✔
273
        label=_("I would like to receive further information"),
274
        help_text=_(
275
            "Projects you are following can send you "
276
            "additional information via email."
277
        ),
278
        required=False,
279
    )
280
    email = forms.EmailField(widget=forms.HiddenInput())
1✔
281

282
    def __init__(self, *args, **kwargs):
1✔
283
        super().__init__(*args, **kwargs)
×
284
        self.prevent_enumeration = False
×
285
        self.fields["username"].help_text = _(
×
286
            "Your username will appear publicly next to your posts."
287
        )
288
        del self.fields["username"].widget.attrs["placeholder"]
×
289

290
    def save(self, request):
1✔
291
        user = super().save(request)
×
292
        user.get_newsletters = self.cleaned_data["get_newsletters"]
×
293
        user.language = get_language()
×
294
        user.save()
×
295
        return user
×
296

297

298
class ChangeUserAdminForm(auth_forms.UserChangeForm):
1✔
299
    def clean_username(self):
1✔
300
        username = self.cleaned_data["username"]
×
301
        try:
×
302
            user = User.objects.get(username__iexact=username)
×
303
            if user != self.instance:
×
304
                raise forms.ValidationError(
×
305
                    User._meta.get_field("username").error_messages["unique"]
306
                )
307
        except User.DoesNotExist:
×
308
            pass
×
309

310
        try:
×
311
            user = User.objects.get(email__iexact=username)
×
312
            if user != self.instance:
×
313
                raise forms.ValidationError(
×
314
                    User._meta.get_field("username").error_messages["used_as_email"]
315
                )
316
        except User.DoesNotExist:
×
317
            pass
×
318

319
        return username
×
320

321

322
class AddUserAdminForm(auth_forms.UserCreationForm):
1✔
323
    def clean_username(self):
1✔
324
        username = self.cleaned_data["username"]
×
325
        user = User.objects.filter(username__iexact=username)
×
326
        if user.exists():
×
327
            raise forms.ValidationError(
×
328
                User._meta.get_field("username").error_messages["unique"]
329
            )
330
        else:
331
            user = User.objects.filter(email__iexact=username)
×
332
            if user.exists():
×
333
                raise forms.ValidationError(
×
334
                    User._meta.get_field("username").error_messages["used_as_email"]
335
                )
336
        return username
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc