Sith/core/views/forms.py

433 lines
14 KiB
Python
Raw Normal View History

# -*- coding:utf-8 -*
#
# 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 Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
2018-07-06 09:35:02 +00:00
from captcha.fields import CaptchaField
2017-06-12 07:42:03 +00:00
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
2015-11-18 16:09:06 +00:00
from django import forms
from django.conf import settings
from django.db import transaction
from django.templatetags.static import static
from django.urls import reverse
from django.core.exceptions import ValidationError
2018-10-04 19:29:19 +00:00
from django.forms import (
CheckboxSelectMultiple,
Select,
DateInput,
TextInput,
DateTimeInput,
Textarea,
)
2017-03-27 12:31:58 +00:00
from django.utils.translation import ugettext_lazy as _
2017-03-27 20:47:24 +00:00
from django.utils.translation import ugettext
2016-08-13 03:33:09 +00:00
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
2016-09-19 18:29:43 +00:00
from ajax_select.fields import AutoCompleteSelectField
from ajax_select import make_ajax_field
from django.utils.dateparse import parse_datetime
from django.utils import timezone
import datetime
from django.forms.utils import to_current_timezone
2016-08-13 03:33:09 +00:00
2016-08-31 00:43:49 +00:00
import re
2015-11-18 16:09:06 +00:00
2017-11-05 23:22:25 +00:00
from core.models import User, Page, SithFile, Gift
2017-06-12 07:42:03 +00:00
from core.utils import resize_image
from io import BytesIO
from PIL import Image
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):
def render(self, name, value, attrs=None, renderer=None):
2016-09-08 01:29:49 +00:00
if attrs:
2018-10-04 19:29:19 +00:00
attrs["class"] = "select_datetime"
2016-09-08 01:29:49 +00:00
else:
2018-10-04 19:29:19 +00:00
attrs = {"class": "select_datetime"}
return super(SelectDateTime, self).render(name, value, attrs, renderer)
2016-09-08 01:29:49 +00:00
2017-06-12 07:42:03 +00:00
class SelectDate(DateInput):
def render(self, name, value, attrs=None, renderer=None):
if attrs:
2018-10-04 19:29:19 +00:00
attrs["class"] = "select_date"
else:
2018-10-04 19:29:19 +00:00
attrs = {"class": "select_date"}
return super(SelectDate, self).render(name, value, attrs, renderer)
2017-06-12 07:42:03 +00:00
class MarkdownInput(Textarea):
template_name = "core/markdown_textarea.jinja"
def get_context(self, name, value, attrs):
context = super(MarkdownInput, self).get_context(name, value, attrs)
context["statics"] = {
2019-05-06 18:42:35 +00:00
"js": static("core/easymde/easymde.min.js"),
"css": static("core/easymde/easymde.min.css"),
}
2018-12-19 13:02:45 +00:00
context["translations"] = {
2018-12-19 13:10:39 +00:00
"heading_smaller": _("Heading"),
2018-12-19 13:02:45 +00:00
"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"),
}
context["markdown_api_url"] = reverse("api:api_markdown")
return context
2017-06-12 07:42:03 +00:00
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"}
2022-04-28 11:16:03 +00:00
output = (
'%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>'
% {
"content": super(SelectFile, self).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">'
+ ugettext("Choose file")
+ "</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"}
2022-04-28 11:16:03 +00:00
output = (
'%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>'
% {
"content": super(SelectUser, self).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">'
+ ugettext("Choose user")
+ "</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):
2018-10-04 19:29:19 +00:00
if "data" in kwargs.keys():
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
2017-06-12 07:42:03 +00:00
except:
pass
2018-10-04 19:29:19 +00:00
kwargs["data"] = data
2016-08-31 00:43:49 +00:00
super(LoginForm, self).__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")
2015-11-18 16:09:06 +00:00
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
2016-08-13 03:33:09 +00:00
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
"""
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,
"profile_pict": forms.ClearableFileInput,
"avatar_pict": forms.ClearableFileInput,
"scrub_pict": forms.ClearableFileInput,
"phone": PhoneNumberInternationalFallbackWidget,
"parent_phone": PhoneNumberInternationalFallbackWidget,
2017-06-12 07:42:03 +00:00
}
labels = {
2018-10-04 19:29:19 +00:00
"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!"),
2017-06-12 07:42:03 +00:00
}
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]
2018-10-04 19:29:19 +00:00
return field_name + str(self.instance.id) + "." + f.content_type.split("/")[-1]
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,
name=self.generate_name(field, f),
2021-11-18 14:14:39 +00:00
file=resize_image(im, 400, f.content_type.split("/")[-1]),
2018-10-04 19:29:19 +00:00
owner=self.instance,
is_folder=False,
mime_type=f.content_type,
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, and gif are accepted"
),
},
)
self._post_clean()
2015-11-18 16:09:06 +00:00
2017-06-12 07:42:03 +00:00
2015-12-09 10:01:11 +00:00
class UserPropForm(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
2015-11-24 13:01:10 +00:00
class Meta:
model = User
2018-10-04 19:29:19 +00:00
fields = ["groups"]
help_texts = {"groups": "Which groups this user belongs to"}
widgets = {"groups": CheckboxSelectMultiple}
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 = AutoCompleteSelectField(
"users", required=True, label=_("Select user"), help_text=None
)
2016-09-19 18:29:43 +00:00
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"]
2018-10-04 19:29:19 +00:00
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")
)
2015-11-27 15:09:47 +00:00
def __init__(self, *arg, **kwargs):
super(PagePropForm, self).__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"]
2018-10-04 19:29:19 +00:00
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")
)
2017-09-12 19:10:32 +00:00
def __init__(self, *args, **kwargs):
super(PageForm, self).__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)
2017-11-05 23:22:25 +00:00
super(GiftForm, self).__init__(*args, **kwargs)
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()
class TzAwareDateTimeField(forms.DateTimeField):
def __init__(
self, input_formats=["%Y-%m-%d %H:%M:%S"], widget=SelectDateTime, **kwargs
):
super().__init__(input_formats=input_formats, widget=widget, **kwargs)
def prepare_value(self, value):
# the db value is a datetime as a string in UTC
if isinstance(value, str):
# convert it into a naive datetime (no timezone attached)
value = parse_datetime(value)
2021-11-18 14:04:25 +00:00
# attach it to the UTC timezone (so that to_current_timezone()) if not None
# converts it to the local timezone)
2021-11-18 14:04:25 +00:00
if value is not None:
value = timezone.make_aware(value, timezone.utc)
if isinstance(value, datetime.datetime):
value = to_current_timezone(value)
# otherwise it is formatted according to locale (in french)
value = str(value)
return value