forms.py 13.6 KB
Newer Older
1
2
3
4
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
5
# - Sli <antoine@bartuccio.fr> #
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
24
from captcha.fields import CaptchaField
Krophil's avatar
Krophil committed
25
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
Skia's avatar
Skia committed
26
from django import forms
Skia's avatar
Skia committed
27
from django.conf import settings
28
from django.db import transaction
Sli's avatar
Sli committed
29
30
from django.templatetags.static import static
from django.core.urlresolvers import reverse
31
from django.core.exceptions import ValidationError
Sli's avatar
Sli committed
32
33
34
35
36
37
38
39
from django.forms import (
    CheckboxSelectMultiple,
    Select,
    DateInput,
    TextInput,
    DateTimeInput,
    Textarea,
)
Skia's avatar
Skia committed
40
from django.utils.translation import ugettext_lazy as _
Sli's avatar
Sli committed
41
from django.utils.translation import ugettext
Skia's avatar
Skia committed
42
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
Skia's avatar
Skia committed
43
from ajax_select.fields import AutoCompleteSelectField
44
from ajax_select import make_ajax_field
Skia's avatar
Skia committed
45

Skia's avatar
Skia committed
46
import re
Skia's avatar
Skia committed
47

Sli's avatar
Sli committed
48
from core.models import User, Page, SithFile, Gift
Krophil's avatar
Krophil committed
49
50
51
52

from core.utils import resize_image
from io import BytesIO
from PIL import Image
53

Skia's avatar
Skia committed
54

55
56
# Widgets

Sli's avatar
Sli committed
57

58
59
60
class SelectSingle(Select):
    def render(self, name, value, attrs=None):
        if attrs:
Sli's avatar
Sli committed
61
            attrs["class"] = "select_single"
62
        else:
Sli's avatar
Sli committed
63
            attrs = {"class": "select_single"}
64
65
        return super(SelectSingle, self).render(name, value, attrs)

Krophil's avatar
Krophil committed
66

67
68
69
class SelectMultiple(Select):
    def render(self, name, value, attrs=None):
        if attrs:
Sli's avatar
Sli committed
70
            attrs["class"] = "select_multiple"
71
        else:
Sli's avatar
Sli committed
72
            attrs = {"class": "select_multiple"}
73
74
        return super(SelectMultiple, self).render(name, value, attrs)

Krophil's avatar
Krophil committed
75

Skia's avatar
Skia committed
76
77
78
class SelectDateTime(DateTimeInput):
    def render(self, name, value, attrs=None):
        if attrs:
Sli's avatar
Sli committed
79
            attrs["class"] = "select_datetime"
Skia's avatar
Skia committed
80
        else:
Sli's avatar
Sli committed
81
            attrs = {"class": "select_datetime"}
Skia's avatar
Skia committed
82
83
        return super(SelectDateTime, self).render(name, value, attrs)

Krophil's avatar
Krophil committed
84

85
86
87
class SelectDate(DateInput):
    def render(self, name, value, attrs=None):
        if attrs:
Sli's avatar
Sli committed
88
            attrs["class"] = "select_date"
89
        else:
Sli's avatar
Sli committed
90
            attrs = {"class": "select_date"}
91
92
        return super(SelectDate, self).render(name, value, attrs)

Krophil's avatar
Krophil committed
93

94
class MarkdownInput(Textarea):
Sli's avatar
Sli committed
95
96
97
98
99
100
101
102
103
    template_name = "core/markdown_textarea.jinja"

    def get_context(self, name, value, attrs):
        context = super(MarkdownInput, self).get_context(name, value, attrs)

        context["statics"] = {
            "js": static("core/simplemde/simplemde.min.js"),
            "css": static("core/simplemde/simplemde.min.css"),
        }
Sli's avatar
Sli committed
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
        context["translations"] = {
            "heading": _("Heading"),
            "italic": _("Italic"),
            "bold": _("Bold"),
            "strikethrough": _("Strikethrough"),
            "underline": _("Underline"),
            "superscript": _("Superscript"),
            "subscript": _("Subscript"),
            "code": _("Code"),
            "quote": _("Quote"),
            "unordered_list": _("Unordered list"),
            "ordered_list": _("Ordered list"),
            "image": _("Insert image"),
            "link": _("Insert link"),
            "table": _("Insert table"),
            "clean_block": _("Clean block"),
            "preview": _("Toggle preview"),
            "side_by_side": _("Toggle side by side"),
            "fullscreen": _("Toggle fullscreen"),
            "guide": _("Markdown guide"),
        }
Sli's avatar
Sli committed
125
126
        context["markdown_api_url"] = reverse("api:api_markdown")
        return context
127

Krophil's avatar
Krophil committed
128

129
130
131
class SelectFile(TextInput):
    def render(self, name, value, attrs=None):
        if attrs:
Sli's avatar
Sli committed
132
            attrs["class"] = "select_file"
133
        else:
Sli's avatar
Sli committed
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
            attrs = {"class": "select_file"}
        output = (
            '%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>'
            % {
                "content": super(SelectFile, self).render(name, value, attrs),
                "title": _("Choose file"),
                "name": name,
            }
        )
        output += (
            '<span name="'
            + name
            + '" class="choose_file_button">'
            + ugettext("Choose file")
            + "</span>"
        )
150
151
        return output

Krophil's avatar
Krophil committed
152

153
154
155
class SelectUser(TextInput):
    def render(self, name, value, attrs=None):
        if attrs:
Sli's avatar
Sli committed
156
            attrs["class"] = "select_user"
157
        else:
Sli's avatar
Sli committed
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
            attrs = {"class": "select_user"}
        output = (
            '%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>'
            % {
                "content": super(SelectUser, self).render(name, value, attrs),
                "title": _("Choose user"),
                "name": name,
            }
        )
        output += (
            '<span name="'
            + name
            + '" class="choose_user_button">'
            + ugettext("Choose user")
            + "</span>"
        )
174
175
        return output

Sli's avatar
Sli committed
176

177
# Forms
Skia's avatar
Skia committed
178

Krophil's avatar
Krophil committed
179

Skia's avatar
Skia committed
180
181
class LoginForm(AuthenticationForm):
    def __init__(self, *arg, **kwargs):
Sli's avatar
Sli committed
182
        if "data" in kwargs.keys():
Skia's avatar
Skia committed
183
            from counter.models import Customer
Sli's avatar
Sli committed
184
185

            data = kwargs["data"].copy()
Skia's avatar
Skia committed
186
            account_code = re.compile(r"^[0-9]+[A-Za-z]$")
Skia's avatar
Skia committed
187
            try:
Sli's avatar
Sli committed
188
189
190
191
192
193
194
195
                if account_code.match(data["username"]):
                    user = (
                        Customer.objects.filter(account_id__iexact=data["username"])
                        .first()
                        .user
                    )
                elif "@" in data["username"]:
                    user = User.objects.filter(email__iexact=data["username"]).first()
Skia's avatar
Skia committed
196
                else:
Sli's avatar
Sli committed
197
198
                    user = User.objects.filter(username=data["username"]).first()
                data["username"] = user.username
Krophil's avatar
Krophil committed
199
200
            except:
                pass
Sli's avatar
Sli committed
201
            kwargs["data"] = data
Skia's avatar
Skia committed
202
        super(LoginForm, self).__init__(*arg, **kwargs)
Sli's avatar
Sli committed
203
        self.fields["username"].label = _("Username, email, or account number")
Skia's avatar
Skia committed
204

Krophil's avatar
Krophil committed
205

Skia's avatar
Skia committed
206
class RegisteringForm(UserCreationForm):
Sli's avatar
Sli committed
207
208
    error_css_class = "error"
    required_css_class = "required"
209
    captcha = CaptchaField()
Krophil's avatar
Krophil committed
210

Skia's avatar
Skia committed
211
212
    class Meta:
        model = User
Sli's avatar
Sli committed
213
        fields = ("first_name", "last_name", "email")
Skia's avatar
Skia committed
214
215
216
217
218
219
220
221
222

    def save(self, commit=True):
        user = super(RegisteringForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        user.generate_username()
        if commit:
            user.save()
        return user

Skia's avatar
Skia committed
223

224
225
226
227
228
229
class UserProfileForm(forms.ModelForm):
    """
    Form handling the user profile, managing the files
    This form is actually pretty bad and was made in the rush before the migration. It should be refactored.
    TODO: refactor this form
    """
Sli's avatar
Sli committed
230

231
232
    class Meta:
        model = User
Sli's avatar
Sli committed
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
        fields = [
            "first_name",
            "last_name",
            "nick_name",
            "email",
            "date_of_birth",
            "profile_pict",
            "avatar_pict",
            "scrub_pict",
            "sex",
            "second_email",
            "address",
            "parent_address",
            "phone",
            "parent_phone",
            "tshirt_size",
            "role",
            "department",
            "dpt_option",
            "semester",
            "quote",
            "school",
            "promo",
            "forum_signature",
            "is_subscriber_viewable",
        ]
259
        widgets = {
Sli's avatar
Sli committed
260
261
262
263
264
265
            "date_of_birth": SelectDate,
            "profile_pict": forms.ClearableFileInput,
            "avatar_pict": forms.ClearableFileInput,
            "scrub_pict": forms.ClearableFileInput,
            "phone": PhoneNumberInternationalFallbackWidget,
            "parent_phone": PhoneNumberInternationalFallbackWidget,
Krophil's avatar
Krophil committed
266
        }
267
        labels = {
Sli's avatar
Sli committed
268
269
270
271
272
            "profile_pict": _(
                "Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)"
            ),
            "avatar_pict": _("Avatar: used on the forum"),
            "scrub_pict": _("Scrub: let other know how your scrub looks like!"),
Krophil's avatar
Krophil committed
273
        }
274
275
276
277
278
279
280
281
282

    def __init__(self, *arg, **kwargs):
        super(UserProfileForm, self).__init__(*arg, **kwargs)

    def full_clean(self):
        super(UserProfileForm, self).full_clean()

    def generate_name(self, field_name, f):
        field_name = field_name[:-4]
Sli's avatar
Sli committed
283
        return field_name + str(self.instance.id) + "." + f.content_type.split("/")[-1]
284
285
286
287
288
289

    def process(self, files):
        avatar = self.instance.avatar_pict
        profile = self.instance.profile_pict
        scrub = self.instance.scrub_pict
        self.full_clean()
Sli's avatar
Sli committed
290
291
292
        self.cleaned_data["avatar_pict"] = avatar
        self.cleaned_data["profile_pict"] = profile
        self.cleaned_data["scrub_pict"] = scrub
293
        parent = SithFile.objects.filter(parent=None, name="profiles").first()
Krophil's avatar
Krophil committed
294
        for field, f in files:
295
296
            with transaction.atomic():
                try:
Skia's avatar
Skia committed
297
                    im = Image.open(BytesIO(f.read()))
Sli's avatar
Sli committed
298
299
300
301
302
303
304
305
306
307
308
                    new_file = SithFile(
                        parent=parent,
                        name=self.generate_name(field, f),
                        file=resize_image(im, 400, f.content_type.split("/")[-1]),
                        owner=self.instance,
                        is_folder=False,
                        mime_type=f.content_type,
                        size=f._size,
                        moderator=self.instance,
                        is_moderated=True,
                    )
Skia's avatar
Skia committed
309
                    new_file.file.name = new_file.name
Sli's avatar
Sli committed
310
311
312
                    old = SithFile.objects.filter(
                        parent=parent, name=new_file.name
                    ).first()
313
314
315
316
317
318
319
320
                    if old:
                        old.delete()
                    new_file.clean()
                    new_file.save()
                    self.cleaned_data[field] = new_file
                    self._errors.pop(field, None)
                except ValidationError as e:
                    self._errors.pop(field, None)
Sli's avatar
Sli committed
321
322
323
324
325
                    self.add_error(
                        field,
                        _("Error uploading file %(file_name)s: %(msg)s")
                        % {"file_name": f, "msg": str(e.message)},
                    )
Skia's avatar
Skia committed
326
327
                except IOError:
                    self._errors.pop(field, None)
Sli's avatar
Sli committed
328
329
330
331
332
333
334
335
336
337
                    self.add_error(
                        field,
                        _("Error uploading file %(file_name)s: %(msg)s")
                        % {
                            "file_name": f,
                            "msg": _(
                                "Bad image format, only jpeg, png, and gif are accepted"
                            ),
                        },
                    )
338
        self._post_clean()
Skia's avatar
Skia committed
339

Krophil's avatar
Krophil committed
340

Skia's avatar
Skia committed
341
class UserPropForm(forms.ModelForm):
Sli's avatar
Sli committed
342
343
    error_css_class = "error"
    required_css_class = "required"
Krophil's avatar
Krophil committed
344

Skia's avatar
Skia committed
345
346
    class Meta:
        model = User
Sli's avatar
Sli committed
347
348
349
        fields = ["groups"]
        help_texts = {"groups": "Which groups this user belongs to"}
        widgets = {"groups": CheckboxSelectMultiple}
Skia's avatar
Skia committed
350

Krophil's avatar
Krophil committed
351

Skia's avatar
Skia committed
352
class UserGodfathersForm(forms.Form):
Sli's avatar
Sli committed
353
354
355
356
357
358
359
    type = forms.ChoiceField(
        choices=[("godfather", _("Godfather")), ("godchild", _("Godchild"))],
        label=_("Add"),
    )
    user = AutoCompleteSelectField(
        "users", required=True, label=_("Select user"), help_text=None
    )
Skia's avatar
Skia committed
360

Krophil's avatar
Krophil committed
361

362
class PagePropForm(forms.ModelForm):
Sli's avatar
Sli committed
363
364
    error_css_class = "error"
    required_css_class = "required"
Krophil's avatar
Krophil committed
365

366
367
    class Meta:
        model = Page
Sli's avatar
Sli committed
368
369
370
371
372
373
374
375
        fields = ["parent", "name", "owner_group", "edit_groups", "view_groups"]

    edit_groups = make_ajax_field(
        Page, "edit_groups", "groups", help_text="", label=_("edit groups")
    )
    view_groups = make_ajax_field(
        Page, "view_groups", "groups", help_text="", label=_("view groups")
    )
376

Skia's avatar
Skia committed
377
378
    def __init__(self, *arg, **kwargs):
        super(PagePropForm, self).__init__(*arg, **kwargs)
Sli's avatar
Sli committed
379
380
        self.fields["edit_groups"].required = False
        self.fields["view_groups"].required = False
Sli's avatar
Sli committed
381
382
383
384
385


class PageForm(forms.ModelForm):
    class Meta:
        model = Page
Sli's avatar
Sli committed
386
387
388
389
390
391
392
393
        fields = ["parent", "name", "owner_group", "edit_groups", "view_groups"]

    edit_groups = make_ajax_field(
        Page, "edit_groups", "groups", help_text="", label=_("edit groups")
    )
    view_groups = make_ajax_field(
        Page, "view_groups", "groups", help_text="", label=_("view groups")
    )
Sli's avatar
Sli committed
394
395
396

    def __init__(self, *args, **kwargs):
        super(PageForm, self).__init__(*args, **kwargs)
Sli's avatar
Sli committed
397
398
399
400
401
        self.fields["parent"].queryset = (
            self.fields["parent"]
            .queryset.exclude(name=settings.SITH_CLUB_ROOT_PAGE)
            .filter(club=None)
        )
Sli's avatar
Sli committed
402
403
404
405
406


class GiftForm(forms.ModelForm):
    class Meta:
        model = Gift
Sli's avatar
Sli committed
407
        fields = ["label", "user"]
Sli's avatar
Sli committed
408
409
410
411

    label = forms.ChoiceField(choices=settings.SITH_GIFT_LIST)

    def __init__(self, *args, **kwargs):
Sli's avatar
Sli committed
412
        user_id = kwargs.pop("user_id", None)
Sli's avatar
Sli committed
413
414
        super(GiftForm, self).__init__(*args, **kwargs)
        if user_id:
Sli's avatar
Sli committed
415
416
417
418
            self.fields["user"].queryset = self.fields["user"].queryset.filter(
                id=user_id
            )
            self.fields["user"].widget = forms.HiddenInput()