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

liqd / adhocracy-plus / 25327419454

04 May 2026 03:20PM UTC coverage: 42.415% (-43.8%) from 86.168%
25327419454

Pull #3075

github

web-flow
Merge 06ba30015 into 15731e8ac
Pull Request #3075: [ST-1932] Breadcrumb Navigation

0 of 31 new or added lines in 1 file covered. (0.0%)

3376 existing lines in 150 files now uncovered.

3302 of 7785 relevant lines covered (42.41%)

0.42 hits per line

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

30.5
/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✔
UNCOV
30
        super().__init__(*args, **kwargs)
×
31

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

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

UNCOV
38
        try:
×
UNCOV
39
            self.fields["captcha"] = ProsopoCaptchaField(label=_("I am not a robot"))
×
UNCOV
40
            self.fields["captcha"].help_text = helpers.add_email_link_to_helptext(
×
41
                self.fields["captcha"].help_text, PROSOPO_CAPTCHA_HELP
42
            )
43
        except Exception as e:
×
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✔
UNCOV
51
        super().__init__(*args, **kwargs)
×
UNCOV
52
        self.fields["login"].label = _("Username/e-mail")
×
UNCOV
53
        del self.fields["login"].widget.attrs["placeholder"]
×
UNCOV
54
        del self.fields["password"].widget.attrs["placeholder"]
×
UNCOV
55
        self.fields["password"].help_text = ""
×
UNCOV
56
        self.fields["login"].widget.attrs["autocomplete"] = "username"
×
UNCOV
57
        self.fields["password"].widget.attrs["autocomplete"] = "current-password"
×
UNCOV
58
        self.fields["password"].widget.attrs["class"] = "password-toggle"
×
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✔
UNCOV
72
        super().__init__(*args, **kwargs)
×
UNCOV
73
        self.fields["username"].help_text = _(
×
74
            "Your username will appear publicly next to your posts."
75
        )
UNCOV
76
        self.fields["email"].widget.attrs["autofocus"] = True
×
UNCOV
77
        del self.fields["username"].widget.attrs["placeholder"]
×
UNCOV
78
        del self.fields["email"].widget.attrs["placeholder"]
×
UNCOV
79
        del self.fields["password1"].widget.attrs["placeholder"]
×
UNCOV
80
        del self.fields["password2"].widget.attrs["placeholder"]
×
UNCOV
81
        self.fields["email"].widget.attrs["autocomplete"] = "username"
×
UNCOV
82
        self.fields["password1"].widget.attrs["autocomplete"] = "new-password"
×
UNCOV
83
        self.fields["password2"].widget.attrs["autocomplete"] = "new-password"
×
UNCOV
84
        self.fields["password1"].widget.attrs["class"] = "password-toggle"
×
UNCOV
85
        self.fields["password2"].widget.attrs["class"] = "password-toggle"
×
86

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

95

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

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

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

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

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

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

162
            if result_str == "true":
×
163
                result = True
×
164

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

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

181
    def clean(self):
1✔
182
        super().clean()
×
183

184
        if any(self.errors):
×
185
            return self.errors
×
186

187
        member_number = self.cleaned_data.get("member_number")
×
188
        birth_date = self.cleaned_data.get("birth_date")
×
189

190
        self.validateMemberNumberAndDate(member_number, birth_date)
×
191

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

208

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

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

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

235

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

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

257
        return username
×
258

259

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