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

liqd / roots / 14241088448

03 Apr 2025 10:45AM UTC coverage: 89.262% (-0.02%) from 89.283%
14241088448

push

github

partizipation
add guest users

leverages lib django-guest-user to add guest user signup and
conversion to regular account.

Closes #ST-32, #ST-33, #ST-34

113 of 134 new or added lines in 11 files covered. (84.33%)

6143 of 6882 relevant lines covered (89.26%)

0.89 hits per line

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

60.63
/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 CaptcheckCaptchaField
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
CAPTCHA_HELP = _(
1✔
23
    "Solve the math problem and click on the correct result.<strong>"
24
    "If you are having difficulty please contact us  by {}email{}.</strong>"
25
)
26

27

28
class DefaultLoginForm(LoginForm):
1✔
29
    def __init__(self, *args, **kwargs):
1✔
30
        super().__init__(*args, **kwargs)
1✔
31
        self.fields["login"].label = _("Username/e-mail")
1✔
32
        del self.fields["login"].widget.attrs["placeholder"]
1✔
33
        del self.fields["password"].widget.attrs["placeholder"]
1✔
34
        self.fields["password"].help_text = ""
1✔
35
        self.fields["login"].widget.attrs["autocomplete"] = "username"
1✔
36
        self.fields["password"].widget.attrs["autocomplete"] = "current-password"
1✔
37

38

39
class TermsAndCaptchaMixin:
1✔
40
    def __init__(self, *args, **kwargs):
1✔
41
        super().__init__(*args, **kwargs)
1✔
42

43
        if "terms_of_use" not in self.fields:
1✔
44
            self.fields["terms_of_use"] = forms.BooleanField(label=_("Terms of use"))
1✔
45

46
        if getattr(settings, "CAPTCHA_URL", None):
1✔
47
            self.fields["captcha"] = CaptcheckCaptchaField(label=_("I am not a robot"))
1✔
48
            self.fields["captcha"].help_text = helpers.add_email_link_to_helptext(
1✔
49
                self.fields["captcha"].help_text, CAPTCHA_HELP
50
            )
51

52

53
class DefaultSignupForm(TermsAndCaptchaMixin, SignupForm):
1✔
54
    get_newsletters = forms.BooleanField(
1✔
55
        label=_("I would like to receive further information"),
56
        help_text=_(
57
            "Projects you are following can send you "
58
            "additional information via email."
59
        ),
60
        required=False,
61
    )
62

63
    def __init__(self, *args, **kwargs):
1✔
64
        super().__init__(*args, **kwargs)
1✔
65

66
        self.fields["username"].help_text = _(
1✔
67
            "Your username will appear publicly next to your posts."
68
        )
69
        self.fields["email"].widget.attrs["autofocus"] = True
1✔
70
        del self.fields["username"].widget.attrs["placeholder"]
1✔
71
        del self.fields["email"].widget.attrs["placeholder"]
1✔
72
        del self.fields["password1"].widget.attrs["placeholder"]
1✔
73
        del self.fields["password2"].widget.attrs["placeholder"]
1✔
74
        self.fields["email"].widget.attrs["autocomplete"] = "username"
1✔
75
        self.fields["password1"].widget.attrs["autocomplete"] = "new-password"
1✔
76
        self.fields["password2"].widget.attrs["autocomplete"] = "new-password"
1✔
77

78
    def save(self, request):
1✔
79
        user = super().save(request)
1✔
80
        if user:
1✔
81
            user.get_newsletters = self.cleaned_data["get_newsletters"]
1✔
82
            user.language = get_language()
1✔
83
            user.save()
1✔
84
            return user
1✔
85

86

87
class GuestCreateForm(TermsAndCaptchaMixin, forms.Form):
1✔
88
    pass
1✔
89

90

91
class GuestConvertForm(DefaultSignupForm):
1✔
92

93
    get_newsletters = forms.BooleanField(
1✔
94
        label=_("I would like to receive further information"),
95
        help_text=_(
96
            "Projects you are following can send you "
97
            "additional information via email."
98
        ),
99
        required=False,
100
    )
101

102
    def __init__(self, *args, **kwargs):
1✔
103
        self.user = kwargs.pop("user", None)
1✔
104
        super().__init__(*args, **kwargs)
1✔
105

106
        del self.fields["captcha"]
1✔
107

108
        self.fields["email"].required = True
1✔
109
        self.fields["username"].required = True
1✔
110
        self.fields["password1"].required = True
1✔
111
        self.fields["password2"].required = True
1✔
112

113
    def clean_email(self):
1✔
114
        email = self.cleaned_data["email"]
1✔
115
        users = User.objects.filter(email__iexact=email)
1✔
116
        if users.exists():
1✔
NEW
117
            raise forms.ValidationError("Email already in use.")
×
118
        return email
1✔
119

120
    def save(self, request):
1✔
121
        user = self.user
1✔
122
        user.email = self.cleaned_data["email"]
1✔
123
        user.username = self.cleaned_data["username"]
1✔
124
        user.get_newsletters = self.cleaned_data["get_newsletters"]
1✔
125
        user.set_password(self.cleaned_data["password1"])
1✔
126
        user.save()
1✔
127

128
        return user
1✔
129

130

131
class IgbceSignupForm(DefaultSignupForm):
1✔
132
    member_number = forms.IntegerField(
1✔
133
        label=_("Membership number of IG BCE"),
134
        help_text=_(
135
            "The membership number consists of a seven-digit number "
136
            "and can be found on the membership card."
137
        ),
138
        max_value=99999999999999999999,
139
        min_value=0,
140
    )
141
    birth_date = forms.DateField(
1✔
142
        label=_("Date of birth"),
143
        help_text=_(
144
            "Please also enter your date of birth in the format "
145
            "MM/DD/YYYY for authentication. Only members of the "
146
            "IG BCE can participate."
147
        ),
148
    )
149
    terms_of_use_extra = forms.BooleanField(
1✔
150
        label=_(
151
            "I confirm that I have read and accepted the "
152
            '<a href="/info/ig-bce-datenschutz/" '
153
            'target="_blank">data protection policy</a> of IG '
154
            "BCE."
155
        )
156
    )
157

158
    def validateMemberNumberAndDate(self, member_number, birth_date):
1✔
159
        if not hasattr(settings, "IGBCE_NAV_URL") or not hasattr(
×
160
            settings, "IGBCE_NAV_SECURITYID"
161
        ):
162
            raise forms.ValidationError(
×
163
                _("Something is wrong with the setup - please try again later")
164
            )
165

166
        if Member.objects.filter(member_number=member_number).exists():
×
167
            raise forms.ValidationError(
×
168
                _(
169
                    "There is already a participant with this membership "
170
                    "number. Please check your entry. If this is your "
171
                    "membership number, please send an email to "
172
                    '"zukunftsgewerkschaft@igbce.de".'
173
                )
174
            )
175

176
        result = False
×
177
        try:
×
178
            client = Client("{}".format(settings.IGBCE_NAV_URL))
×
179
            parameters = "{};{}".format(member_number, birth_date.strftime("%d.%m.%Y"))
×
180
            connection_parameters = (
×
181
                "ObjectID:0;SecurityID:{};SetSize:0;UnitopProxyVersion:2.0".format(
182
                    settings.IGBCE_NAV_SECURITYID
183
                )
184
            )
185

186
            response = client.service.SendRequest(
×
187
                functionName="CALCONNECT_MemberNoBirthDate",
188
                functionParameters=parameters,
189
                filters="",
190
                connectionParameters=connection_parameters,
191
            )
192

193
            result_str = xmltodict.parse(response)["Response"]["ResponseData"][
×
194
                "Object"
195
            ]["CalConnectorResult"]
196

197
            if result_str == "true":
×
198
                result = True
×
199

200
        except BaseException:
×
201
            logger.exception("IGBCE API error")
×
202
            raise forms.ValidationError(
×
203
                _("Something is wrong with the setup - please try again later")
204
            )
205

206
        if not result:
×
207
            raise forms.ValidationError(
×
208
                _(
209
                    "Unfortunately, the member number and / or date of birth "
210
                    "could not be linked to an active member account. Please "
211
                    "check your input and try again. If you still have "
212
                    'problems, please contact "zukunftsgewerkschaft@igbce.de".'
213
                )
214
            )
215

216
    def clean(self):
1✔
217
        super().clean()
×
218

219
        if any(self.errors):
×
220
            return self.errors
×
221

222
        member_number = self.cleaned_data.get("member_number")
×
223
        birth_date = self.cleaned_data.get("birth_date")
×
224

225
        self.validateMemberNumberAndDate(member_number, birth_date)
×
226

227
    def save(self, request):
1✔
228
        user = super().save(request)
×
229
        if hasattr(settings, "SITE_ORGANISATION_SLUG"):
×
230
            organisation = Organisation.objects.get(
×
231
                slug=settings.SITE_ORGANISATION_SLUG
232
            )
233
            additional_info = {"birth_date": self.cleaned_data["birth_date"]}
×
234
            member = Member(
×
235
                member=user,
236
                member_number=self.cleaned_data["member_number"],
237
                organisation=organisation,
238
                additional_info=additional_info,
239
            )
240
            member.save()
×
241
        return user
×
242

243

244
class SocialTermsSignupForm(SocialSignupForm):
1✔
245
    terms_of_use = forms.BooleanField(label=_("Terms of use"))
1✔
246
    get_newsletters = forms.BooleanField(
1✔
247
        label=_("I would like to receive further information"),
248
        help_text=_(
249
            "Projects you are following can send you "
250
            "additional information via email."
251
        ),
252
        required=False,
253
    )
254
    email = forms.EmailField(widget=forms.HiddenInput())
1✔
255

256
    def __init__(self, *args, **kwargs):
1✔
257
        super().__init__(*args, **kwargs)
×
258
        self.prevent_enumeration = False
×
259
        self.fields["username"].help_text = _(
×
260
            "Your username will appear publicly next to your posts."
261
        )
262
        del self.fields["username"].widget.attrs["placeholder"]
×
263

264
    def save(self, request):
1✔
265
        user = super().save(request)
×
266
        user.get_newsletters = self.cleaned_data["get_newsletters"]
×
267
        user.language = get_language()
×
268
        user.save()
×
269
        return user
×
270

271

272
class ChangeUserAdminForm(auth_forms.UserChangeForm):
1✔
273
    def clean_username(self):
1✔
274
        username = self.cleaned_data["username"]
×
275
        try:
×
276
            user = User.objects.get(username__iexact=username)
×
277
            if user != self.instance:
×
278
                raise forms.ValidationError(
×
279
                    User._meta.get_field("username").error_messages["unique"]
280
                )
281
        except User.DoesNotExist:
×
282
            pass
×
283

284
        try:
×
285
            user = User.objects.get(email__iexact=username)
×
286
            if user != self.instance:
×
287
                raise forms.ValidationError(
×
288
                    User._meta.get_field("username").error_messages["used_as_email"]
289
                )
290
        except User.DoesNotExist:
×
291
            pass
×
292

293
        return username
×
294

295

296
class AddUserAdminForm(auth_forms.UserCreationForm):
1✔
297
    def clean_username(self):
1✔
298
        username = self.cleaned_data["username"]
×
299
        user = User.objects.filter(username__iexact=username)
×
300
        if user.exists():
×
301
            raise forms.ValidationError(
×
302
                User._meta.get_field("username").error_messages["unique"]
303
            )
304
        else:
305
            user = User.objects.filter(email__iexact=username)
×
306
            if user.exists():
×
307
                raise forms.ValidationError(
×
308
                    User._meta.get_field("username").error_messages["used_as_email"]
309
                )
310
        return username
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc