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

liqd / adhocracy-plus / 18100763815

29 Sep 2025 02:39PM UTC coverage: 89.146% (-0.1%) from 89.261%
18100763815

push

github

web-flow
[ST-887] app/captcha: change to prosopo captcha (#2977)

25 of 43 new or added lines in 5 files covered. (58.14%)

1 existing line in 1 file now uncovered.

6053 of 6790 relevant lines covered (89.15%)

0.89 hits per line

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

52.9
/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 TermsAndCaptchaMixin:
1✔
29
    def __init__(self, *args, **kwargs):
1✔
30
        super().__init__(*args, **kwargs)
1✔
31

32
        if "terms_of_use" not in self.fields:
1✔
33
            self.fields["terms_of_use"] = forms.BooleanField(label=_("Terms of use"))
×
34

35
        if not getattr(settings, "CAPTCHA", False):
1✔
36
            return  # Don't add captcha field
1✔
37

38
        try:
1✔
39
            self.fields["captcha"] = ProsopoCaptchaField(label=_("I am not a robot"))
1✔
40
            self.fields["captcha"].help_text = helpers.add_email_link_to_helptext(
1✔
41
                self.fields["captcha"].help_text, PROSOPO_CAPTCHA_HELP
42
            )
NEW
43
        except Exception as e:
×
NEW
44
            logger.warning(
×
45
                f"Could not add Prosopo captcha field, are the PROSOPO_SITE_KEY and PROSOPO_SECRET_KEY set : {e}"
46
            )
47

48

49
class DefaultLoginForm(LoginForm):
1✔
50
    def __init__(self, *args, **kwargs):
1✔
51
        super().__init__(*args, **kwargs)
1✔
52
        self.fields["login"].label = _("Username/e-mail")
1✔
53
        del self.fields["login"].widget.attrs["placeholder"]
1✔
54
        del self.fields["password"].widget.attrs["placeholder"]
1✔
55
        self.fields["password"].help_text = ""
1✔
56
        self.fields["login"].widget.attrs["autocomplete"] = "username"
1✔
57
        self.fields["password"].widget.attrs["autocomplete"] = "current-password"
1✔
58

59

60
class DefaultSignupForm(TermsAndCaptchaMixin, SignupForm):
1✔
61
    terms_of_use = forms.BooleanField(label=_("Terms of use"))
1✔
62
    get_newsletters = forms.BooleanField(
1✔
63
        label=_("I would like to receive further information"),
64
        help_text=_(
65
            "Projects you are following can send you "
66
            "additional information via email."
67
        ),
68
        required=False,
69
    )
70

71
    def __init__(self, *args, **kwargs):
1✔
72
        super().__init__(*args, **kwargs)
1✔
73
        self.fields["username"].help_text = _(
1✔
74
            "Your username will appear publicly next to your posts."
75
        )
76
        self.fields["email"].widget.attrs["autofocus"] = True
1✔
77
        del self.fields["username"].widget.attrs["placeholder"]
1✔
78
        del self.fields["email"].widget.attrs["placeholder"]
1✔
79
        del self.fields["password1"].widget.attrs["placeholder"]
1✔
80
        del self.fields["password2"].widget.attrs["placeholder"]
1✔
81
        self.fields["email"].widget.attrs["autocomplete"] = "username"
1✔
82
        self.fields["password1"].widget.attrs["autocomplete"] = "new-password"
1✔
83
        self.fields["password2"].widget.attrs["autocomplete"] = "new-password"
1✔
84

85
    def save(self, request):
1✔
86
        user = super().save(request)
1✔
87
        if user:
1✔
88
            user.get_newsletters = self.cleaned_data["get_newsletters"]
1✔
89
            user.language = get_language()
1✔
90
            user.save()
1✔
91
            return user
1✔
92

93

94
class IgbceSignupForm(DefaultSignupForm):
1✔
95
    member_number = forms.IntegerField(
1✔
96
        label=_("Membership number of IG BCE"),
97
        help_text=_(
98
            "The membership number consists of a seven-digit number "
99
            "and can be found on the membership card."
100
        ),
101
        max_value=99999999999999999999,
102
        min_value=0,
103
    )
104
    birth_date = forms.DateField(
1✔
105
        label=_("Date of birth"),
106
        help_text=_(
107
            "Please also enter your date of birth in the format "
108
            "MM/DD/YYYY for authentication. Only members of the "
109
            "IG BCE can participate."
110
        ),
111
    )
112
    terms_of_use_extra = forms.BooleanField(
1✔
113
        label=_(
114
            "I confirm that I have read and accepted the "
115
            '<a href="/info/ig-bce-datenschutz/" '
116
            'target="_blank">data protection policy</a> of IG '
117
            "BCE."
118
        )
119
    )
120

121
    def validateMemberNumberAndDate(self, member_number, birth_date):
1✔
122
        if not hasattr(settings, "IGBCE_NAV_URL") or not hasattr(
×
123
            settings, "IGBCE_NAV_SECURITYID"
124
        ):
125
            raise forms.ValidationError(
×
126
                _("Something is wrong with the setup - please try again later")
127
            )
128

129
        if Member.objects.filter(member_number=member_number).exists():
×
130
            raise forms.ValidationError(
×
131
                _(
132
                    "There is already a participant with this membership "
133
                    "number. Please check your entry. If this is your "
134
                    "membership number, please send an email to "
135
                    '"zukunftsgewerkschaft@igbce.de".'
136
                )
137
            )
138

139
        result = False
×
140
        try:
×
141
            client = Client("{}".format(settings.IGBCE_NAV_URL))
×
142
            parameters = "{};{}".format(member_number, birth_date.strftime("%d.%m.%Y"))
×
143
            connection_parameters = (
×
144
                "ObjectID:0;SecurityID:{};SetSize:0;UnitopProxyVersion:2.0".format(
145
                    settings.IGBCE_NAV_SECURITYID
146
                )
147
            )
148

149
            response = client.service.SendRequest(
×
150
                functionName="CALCONNECT_MemberNoBirthDate",
151
                functionParameters=parameters,
152
                filters="",
153
                connectionParameters=connection_parameters,
154
            )
155

156
            result_str = xmltodict.parse(response)["Response"]["ResponseData"][
×
157
                "Object"
158
            ]["CalConnectorResult"]
159

160
            if result_str == "true":
×
161
                result = True
×
162

163
        except BaseException:
×
164
            logger.exception("IGBCE API error")
×
165
            raise forms.ValidationError(
×
166
                _("Something is wrong with the setup - please try again later")
167
            )
168

169
        if not result:
×
170
            raise forms.ValidationError(
×
171
                _(
172
                    "Unfortunately, the member number and / or date of birth "
173
                    "could not be linked to an active member account. Please "
174
                    "check your input and try again. If you still have "
175
                    'problems, please contact "zukunftsgewerkschaft@igbce.de".'
176
                )
177
            )
178

179
    def clean(self):
1✔
180
        super().clean()
×
181

182
        if any(self.errors):
×
183
            return self.errors
×
184

185
        member_number = self.cleaned_data.get("member_number")
×
186
        birth_date = self.cleaned_data.get("birth_date")
×
187

188
        self.validateMemberNumberAndDate(member_number, birth_date)
×
189

190
    def save(self, request):
1✔
191
        user = super().save(request)
×
192
        if hasattr(settings, "SITE_ORGANISATION_SLUG"):
×
193
            organisation = Organisation.objects.get(
×
194
                slug=settings.SITE_ORGANISATION_SLUG
195
            )
196
            additional_info = {"birth_date": self.cleaned_data["birth_date"]}
×
197
            member = Member(
×
198
                member=user,
199
                member_number=self.cleaned_data["member_number"],
200
                organisation=organisation,
201
                additional_info=additional_info,
202
            )
203
            member.save()
×
204
        return user
×
205

206

207
class SocialTermsSignupForm(TermsAndCaptchaMixin, SocialSignupForm):
1✔
208
    get_newsletters = forms.BooleanField(
1✔
209
        label=_("I would like to receive further information"),
210
        help_text=_(
211
            "Projects you are following can send you "
212
            "additional information via email."
213
        ),
214
        required=False,
215
    )
216
    email = forms.EmailField(widget=forms.HiddenInput())
1✔
217

218
    def __init__(self, *args, **kwargs):
1✔
219
        super().__init__(*args, **kwargs)
×
220
        self.prevent_enumeration = False
×
221
        self.fields["username"].help_text = _(
×
222
            "Your username will appear publicly next to your posts."
223
        )
224
        del self.fields["username"].widget.attrs["placeholder"]
×
225

226
    def save(self, request):
1✔
227
        user = super().save(request)
×
228
        user.get_newsletters = self.cleaned_data["get_newsletters"]
×
229
        user.language = get_language()
×
230
        user.save()
×
231
        return user
×
232

233

234
class ChangeUserAdminForm(auth_forms.UserChangeForm):
1✔
235
    def clean_username(self):
1✔
236
        username = self.cleaned_data["username"]
×
237
        try:
×
238
            user = User.objects.get(username__iexact=username)
×
239
            if user != self.instance:
×
240
                raise forms.ValidationError(
×
241
                    User._meta.get_field("username").error_messages["unique"]
242
                )
243
        except User.DoesNotExist:
×
244
            pass
×
245

246
        try:
×
247
            user = User.objects.get(email__iexact=username)
×
248
            if user != self.instance:
×
249
                raise forms.ValidationError(
×
250
                    User._meta.get_field("username").error_messages["used_as_email"]
251
                )
252
        except User.DoesNotExist:
×
253
            pass
×
254

255
        return username
×
256

257

258
class AddUserAdminForm(auth_forms.UserCreationForm):
1✔
259
    def clean_username(self):
1✔
260
        username = self.cleaned_data["username"]
×
261
        user = User.objects.filter(username__iexact=username)
×
262
        if user.exists():
×
263
            raise forms.ValidationError(
×
264
                User._meta.get_field("username").error_messages["unique"]
265
            )
266
        else:
267
            user = User.objects.filter(email__iexact=username)
×
268
            if user.exists():
×
269
                raise forms.ValidationError(
×
270
                    User._meta.get_field("username").error_messages["used_as_email"]
271
                )
272
        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