Sith/core/views/forms.py

401 lines
13 KiB
Python
Raw Normal View History

#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# - Sli <antoine@bartuccio.fr> #
# 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 Software Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
2024-06-24 11:07:36 +00:00
import re
from io import BytesIO
2018-07-06 09:35:02 +00:00
from captcha.fields import CaptchaField
2015-11-18 16:09:06 +00:00
from django import forms
from django.conf import settings
2024-06-24 11:07:36 +00:00
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
2024-11-14 11:01:57 +00:00
from django.contrib.staticfiles.management.commands.collectstatic import (
staticfiles_storage,
)
from django.core.exceptions import ValidationError
2024-06-24 11:07:36 +00:00
from django.db import transaction
from django.forms import CheckboxSelectMultiple, DateInput, DateTimeInput, TextInput
from django.utils.translation import gettext
2024-06-24 11:07:36 +00:00
from django.utils.translation import gettext_lazy as _
2024-07-26 17:41:11 +00:00
from phonenumber_field.widgets import RegionalPhoneNumberWidget
2017-06-12 07:42:03 +00:00
from PIL import Image
from antispam.forms import AntiSpamEmailField
from core.models import Gift, Group, Page, SithFile, User
2024-06-24 11:07:36 +00:00
from core.utils import resize_image
from core.views.widgets.select import (
AutoCompleteSelect,
AutoCompleteSelectGroup,
AutoCompleteSelectMultipleGroup,
AutoCompleteSelectUser,
)
2016-08-13 03:33:09 +00:00
# Widgets
2018-10-04 19:29:19 +00:00
2016-09-08 01:29:49 +00:00
class SelectDateTime(DateTimeInput):
2024-07-21 14:16:40 +00:00
def __init__(self, attrs=None, format=None): # noqa A002
default = {"type": "datetime-local"}
attrs = default if attrs is None else default | attrs
super().__init__(attrs=attrs, format=format or "%Y-%m-%d %H:%M")
2016-09-08 01:29:49 +00:00
2017-06-12 07:42:03 +00:00
class SelectDate(DateInput):
2024-07-21 14:16:40 +00:00
def __init__(self, attrs=None, format=None): # noqa A002
default = {"type": "date"}
attrs = default if attrs is None else default | attrs
super().__init__(attrs=attrs, format=format or "%Y-%m-%d")
2017-06-12 07:42:03 +00:00
class NFCTextInput(TextInput):
template_name = "core/widgets/nfc.jinja"
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
2024-11-14 11:01:57 +00:00
context["statics"] = {
"js": staticfiles_storage.url("bundled/core/components/nfc-input-index.ts"),
"css": staticfiles_storage.url("core/components/nfc-input.scss"),
2024-11-14 11:01:57 +00:00
}
return context
class SelectFile(TextInput):
def render(self, name, value, attrs=None, renderer=None):
if attrs:
2018-10-04 19:29:19 +00:00
attrs["class"] = "select_file"
else:
2018-10-04 19:29:19 +00:00
attrs = {"class": "select_file"}
output = (
'%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>'
% {
2024-06-27 12:46:43 +00:00
"content": super().render(name, value, attrs, renderer),
"title": _("Choose file"),
"name": name,
}
)
2018-10-04 19:29:19 +00:00
output += (
'<span name="'
+ name
+ '" class="choose_file_button">'
+ gettext("Choose file")
2018-10-04 19:29:19 +00:00
+ "</span>"
)
return output
2017-06-12 07:42:03 +00:00
class SelectUser(TextInput):
def render(self, name, value, attrs=None, renderer=None):
if attrs:
2018-10-04 19:29:19 +00:00
attrs["class"] = "select_user"
else:
2018-10-04 19:29:19 +00:00
attrs = {"class": "select_user"}
output = (
'%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>'
% {
2024-06-27 12:46:43 +00:00
"content": super().render(name, value, attrs, renderer),
"title": _("Choose user"),
"name": name,
}
)
2018-10-04 19:29:19 +00:00
output += (
'<span name="'
+ name
+ '" class="choose_user_button">'
+ gettext("Choose user")
2018-10-04 19:29:19 +00:00
+ "</span>"
)
return output
2018-10-04 19:29:19 +00:00
# Forms
2015-11-18 08:44:06 +00:00
2017-06-12 07:42:03 +00:00
2016-08-31 00:43:49 +00:00
class LoginForm(AuthenticationForm):
def __init__(self, *arg, **kwargs):
if "data" in kwargs:
2016-08-31 00:43:49 +00:00
from counter.models import Customer
2018-10-04 19:29:19 +00:00
data = kwargs["data"].copy()
2016-08-31 00:43:49 +00:00
account_code = re.compile(r"^[0-9]+[A-Za-z]$")
2016-09-01 15:50:13 +00:00
try:
2018-10-04 19:29:19 +00:00
if account_code.match(data["username"]):
user = (
2021-11-18 14:14:39 +00:00
Customer.objects.filter(account_id__iexact=data["username"])
2018-10-04 19:29:19 +00:00
.first()
.user
)
elif "@" in data["username"]:
2021-11-18 14:14:39 +00:00
user = User.objects.filter(email__iexact=data["username"]).first()
2016-09-01 15:50:13 +00:00
else:
2021-11-18 14:14:39 +00:00
user = User.objects.filter(username=data["username"]).first()
2018-10-04 19:29:19 +00:00
data["username"] = user.username
except: # noqa E722 I don't know what error is supposed to be raised here
2017-06-12 07:42:03 +00:00
pass
2018-10-04 19:29:19 +00:00
kwargs["data"] = data
2024-06-27 12:46:43 +00:00
super().__init__(*arg, **kwargs)
2018-10-04 19:29:19 +00:00
self.fields["username"].label = _("Username, email, or account number")
2016-08-31 00:43:49 +00:00
2017-06-12 07:42:03 +00:00
2015-11-18 08:44:06 +00:00
class RegisteringForm(UserCreationForm):
2018-10-04 19:29:19 +00:00
error_css_class = "error"
required_css_class = "required"
2018-07-06 09:35:02 +00:00
captcha = CaptchaField()
2017-06-12 07:42:03 +00:00
2015-11-18 08:44:06 +00:00
class Meta:
model = User
2018-10-04 19:29:19 +00:00
fields = ("first_name", "last_name", "email")
field_classes = {"email": AntiSpamEmailField}
2015-11-18 16:09:06 +00:00
2016-08-13 03:33:09 +00:00
class UserProfileForm(forms.ModelForm):
"""Form handling the user profile, managing the files"""
2018-10-04 19:29:19 +00:00
class Meta:
model = User
2018-10-04 19:29:19 +00:00
fields = [
"first_name",
"last_name",
"nick_name",
"email",
"date_of_birth",
"profile_pict",
"avatar_pict",
"scrub_pict",
"sex",
"pronouns",
2018-10-04 19:29:19 +00:00
"second_email",
"address",
"parent_address",
"phone",
"parent_phone",
"tshirt_size",
"role",
"department",
"dpt_option",
"semester",
"quote",
"school",
"promo",
"forum_signature",
"is_subscriber_viewable",
]
widgets = {
2018-10-04 19:29:19 +00:00
"date_of_birth": SelectDate,
2024-07-26 17:41:11 +00:00
"phone": RegionalPhoneNumberWidget,
"parent_phone": RegionalPhoneNumberWidget,
"quote": forms.Textarea,
2017-06-12 07:42:03 +00:00
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Image fields are injected here to override the file field provided by the model
# This would be better if we could have a SithImage sort of model input instead of a generic SithFile
self.fields["profile_pict"] = forms.ImageField(
required=False,
label=_(
"Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)"
),
)
self.fields["avatar_pict"] = forms.ImageField(
required=False,
label=_("Avatar: used on the forum"),
)
self.fields["scrub_pict"] = forms.ImageField(
required=False,
label=_("Scrub: let other know how your scrub looks like!"),
)
def process(self, files):
avatar = self.instance.avatar_pict
profile = self.instance.profile_pict
scrub = self.instance.scrub_pict
self.full_clean()
2018-10-04 19:29:19 +00:00
self.cleaned_data["avatar_pict"] = avatar
self.cleaned_data["profile_pict"] = profile
self.cleaned_data["scrub_pict"] = scrub
parent = SithFile.objects.filter(parent=None, name="profiles").first()
2017-06-12 07:42:03 +00:00
for field, f in files:
with transaction.atomic():
try:
2016-08-13 03:33:09 +00:00
im = Image.open(BytesIO(f.read()))
2018-10-04 19:29:19 +00:00
new_file = SithFile(
parent=parent,
2024-09-01 17:05:54 +00:00
name=f"{field.removesuffix('_pict')}_{self.instance.id}.webp",
file=resize_image(im, 400, "webp"),
2018-10-04 19:29:19 +00:00
owner=self.instance,
is_folder=False,
2024-09-01 17:05:54 +00:00
mime_type="image/wepb",
size=f.size,
2018-10-04 19:29:19 +00:00
moderator=self.instance,
is_moderated=True,
)
2016-08-13 03:33:09 +00:00
new_file.file.name = new_file.name
2018-10-04 19:29:19 +00:00
old = SithFile.objects.filter(
parent=parent, name=new_file.name
).first()
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)
2018-10-04 19:29:19 +00:00
self.add_error(
field,
_("Error uploading file %(file_name)s: %(msg)s")
% {"file_name": f, "msg": str(e.message)},
)
2016-08-13 03:33:09 +00:00
except IOError:
self._errors.pop(field, None)
2018-10-04 19:29:19 +00:00
self.add_error(
field,
_("Error uploading file %(file_name)s: %(msg)s")
% {
"file_name": f,
"msg": _(
"Bad image format, only jpeg, png, webp and gif are accepted"
2018-10-04 19:29:19 +00:00
),
},
)
self._post_clean()
2015-11-18 16:09:06 +00:00
2017-06-12 07:42:03 +00:00
2024-12-20 10:00:57 +00:00
class UserGroupsForm(forms.ModelForm):
2018-10-04 19:29:19 +00:00
error_css_class = "error"
required_css_class = "required"
2017-06-12 07:42:03 +00:00
2024-12-20 10:00:57 +00:00
groups = forms.ModelMultipleChoiceField(
queryset=Group.objects.filter(is_manually_manageable=True),
widget=CheckboxSelectMultiple,
2024-12-20 10:00:57 +00:00
label=_("Groups"),
)
2015-11-24 13:01:10 +00:00
class Meta:
model = User
2018-10-04 19:29:19 +00:00
fields = ["groups"]
2015-11-24 13:01:10 +00:00
2017-06-12 07:42:03 +00:00
2016-09-19 18:29:43 +00:00
class UserGodfathersForm(forms.Form):
2018-10-04 19:29:19 +00:00
type = forms.ChoiceField(
choices=[
("godfather", _("Godfather / Godmother")),
("godchild", _("Godchild")),
],
2018-10-04 19:29:19 +00:00
label=_("Add"),
)
user = forms.ModelChoiceField(
label=_("Select user"),
help_text=None,
required=True,
widget=AutoCompleteSelectUser,
queryset=User.objects.all(),
2018-10-04 19:29:19 +00:00
)
2016-09-19 18:29:43 +00:00
def __init__(self, *args, user: User, **kwargs):
super().__init__(*args, **kwargs)
self.target_user = user
def clean_user(self):
other_user = self.cleaned_data.get("user")
if not other_user:
raise ValidationError(_("This user does not exist"))
if other_user == self.target_user:
raise ValidationError(_("You cannot be related to yourself"))
return other_user
def clean(self):
super().clean()
if not self.is_valid():
return self.cleaned_data
other_user = self.cleaned_data["user"]
if self.cleaned_data["type"] == "godfather":
if self.target_user.godfathers.contains(other_user):
self.add_error(
"user",
_("%s is already your godfather") % (other_user.get_short_name()),
)
else:
if self.target_user.godchildren.contains(other_user):
self.add_error(
"user",
_("%s is already your godchild") % (other_user.get_short_name()),
)
return self.cleaned_data
2017-06-12 07:42:03 +00:00
class PagePropForm(forms.ModelForm):
2018-10-04 19:29:19 +00:00
error_css_class = "error"
required_css_class = "required"
2017-06-12 07:42:03 +00:00
class Meta:
model = Page
2021-11-18 14:14:39 +00:00
fields = ["parent", "name", "owner_group", "edit_groups", "view_groups"]
widgets = {
"parent": AutoCompleteSelect,
"owner_group": AutoCompleteSelectGroup,
"edit_groups": AutoCompleteSelectMultipleGroup,
"view_groups": AutoCompleteSelectMultipleGroup,
}
2015-11-27 15:09:47 +00:00
def __init__(self, *arg, **kwargs):
2024-06-27 12:46:43 +00:00
super().__init__(*arg, **kwargs)
2018-10-04 19:29:19 +00:00
self.fields["edit_groups"].required = False
self.fields["view_groups"].required = False
2017-09-12 19:10:32 +00:00
class PageForm(forms.ModelForm):
class Meta:
model = Page
2021-11-18 14:14:39 +00:00
fields = ["parent", "name", "owner_group", "edit_groups", "view_groups"]
widgets = {
"parent": AutoCompleteSelect,
"owner_group": AutoCompleteSelectGroup,
"edit_groups": AutoCompleteSelectMultipleGroup,
"view_groups": AutoCompleteSelectMultipleGroup,
}
2017-09-12 19:10:32 +00:00
def __init__(self, *args, **kwargs):
2024-06-27 12:46:43 +00:00
super().__init__(*args, **kwargs)
2018-10-04 19:29:19 +00:00
self.fields["parent"].queryset = (
self.fields["parent"]
.queryset.exclude(name=settings.SITH_CLUB_ROOT_PAGE)
.filter(club=None)
)
2017-11-05 23:22:25 +00:00
class GiftForm(forms.ModelForm):
class Meta:
model = Gift
2018-10-04 19:29:19 +00:00
fields = ["label", "user"]
2017-11-05 23:22:25 +00:00
label = forms.ChoiceField(choices=settings.SITH_GIFT_LIST)
def __init__(self, *args, **kwargs):
2018-10-04 19:29:19 +00:00
user_id = kwargs.pop("user_id", None)
2024-06-27 12:46:43 +00:00
super().__init__(*args, **kwargs)
2017-11-05 23:22:25 +00:00
if user_id:
2018-10-04 19:29:19 +00:00
self.fields["user"].queryset = self.fields["user"].queryset.filter(
id=user_id
)
self.fields["user"].widget = forms.HiddenInput()