lib.itmens/common/forms.py

296 lines
7.8 KiB
Python
Raw Normal View History

2020-05-01 22:46:15 +08:00
from django import forms
2021-12-20 22:59:32 -05:00
from markdownx.fields import MarkdownxFormField
2020-10-03 23:27:41 +02:00
import django.contrib.postgres.forms as postgres
from django.utils import formats
2020-05-05 23:50:48 +08:00
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
2020-05-01 22:46:15 +08:00
import json
class KeyValueInput(forms.Widget):
2021-02-15 21:27:50 +01:00
"""
Input widget for Json field
"""
2023-01-11 19:11:31 -05:00
template_name = "widgets/hstore.html"
2020-05-01 22:46:15 +08:00
def get_context(self, name, value, attrs):
2021-02-15 21:27:50 +01:00
context = super().get_context(name, value, attrs)
data = None
2023-01-11 19:11:31 -05:00
if context["widget"]["value"] is not None:
data = json.loads(context["widget"]["value"])
context["widget"]["value"] = (
[{p[0]: p[1]} for p in data.items()] if data else []
)
2020-05-05 23:50:48 +08:00
return context
2020-05-01 22:46:15 +08:00
2020-10-03 23:27:41 +02:00
class Media:
2023-01-11 19:11:31 -05:00
js = ("js/key_value_input.js",)
2020-10-03 23:27:41 +02:00
2021-02-15 21:27:50 +01:00
class HstoreInput(forms.Widget):
"""
Input widget for Hstore field
"""
2023-01-11 19:11:31 -05:00
template_name = "widgets/hstore.html"
2021-02-15 21:27:50 +01:00
def format_value(self, value):
"""
Return a value as it should appear when rendered in a template.
"""
2023-01-11 19:11:31 -05:00
if value == "" or value is None:
2021-02-15 21:27:50 +01:00
return None
if self.is_localized:
return formats.localize_input(value)
# do not return str
return value
class Media:
2023-01-11 19:11:31 -05:00
js = ("js/key_value_input.js",)
2021-02-15 21:27:50 +01:00
2021-12-24 11:56:04 -08:00
class JSONField(forms.fields.JSONField):
2020-10-03 23:27:41 +02:00
widget = KeyValueInput
2023-01-11 19:11:31 -05:00
2020-10-03 23:27:41 +02:00
def to_python(self, value):
if not value:
return None
2021-12-15 21:54:24 -05:00
j = {}
2020-10-11 20:07:32 +02:00
if isinstance(value, dict):
2021-12-15 21:54:24 -05:00
j = value
2020-10-11 20:07:32 +02:00
else:
2023-01-11 19:11:31 -05:00
pairs = json.loads("[" + value + "]")
2020-10-11 20:07:32 +02:00
if isinstance(pairs, dict):
2021-12-15 21:54:24 -05:00
j = pairs
2020-10-11 20:07:32 +02:00
else:
# list or tuple
for pair in pairs:
2021-12-15 21:54:24 -05:00
j = {**j, **pair}
return super().to_python(j)
2020-10-03 23:27:41 +02:00
2020-05-01 22:46:15 +08:00
2020-05-05 23:50:48 +08:00
class RadioBooleanField(forms.ChoiceField):
widget = forms.RadioSelect
def to_python(self, value):
"""Return a Python boolean object."""
# Explicitly check for the string 'False', which is what a hidden field
# will submit for False. Also check for '0', since this is what
# RadioSelect will provide. Because bool("True") == bool('1') == True,
# we don't need to handle that explicitly.
2023-01-11 19:11:31 -05:00
if isinstance(value, str) and value.lower() in ("false", "0"):
2020-05-05 23:50:48 +08:00
value = False
else:
value = bool(value)
return value
2020-05-05 23:50:48 +08:00
2020-05-01 22:46:15 +08:00
2020-05-05 23:50:48 +08:00
class RatingValidator:
2023-01-11 19:11:31 -05:00
"""empty value is not validated"""
2020-05-05 23:50:48 +08:00
def __call__(self, value):
if not isinstance(value, int):
raise ValidationError(
2023-01-11 19:11:31 -05:00
_("%(value)s is not an integer"),
params={"value": value},
2020-05-05 23:50:48 +08:00
)
if not str(value) in [str(i) for i in range(0, 11)]:
2020-05-05 23:50:48 +08:00
raise ValidationError(
2023-01-11 19:11:31 -05:00
_("%(value)s is not an integer in range 1-10"),
params={"value": value},
2020-05-12 14:05:12 +08:00
)
2020-07-03 15:36:23 +08:00
class PreviewImageInput(forms.FileInput):
2023-01-11 19:11:31 -05:00
template_name = "widgets/image.html"
2020-05-12 14:05:12 +08:00
def format_value(self, value):
"""
Return the file object if it has a defined url attribute.
"""
if self.is_initial(value):
2020-05-12 14:37:12 +08:00
if value.url:
return value.url
else:
return
2020-05-12 14:05:12 +08:00
def is_initial(self, value):
"""
Return whether value is considered to be initial value.
"""
2023-01-11 19:11:31 -05:00
return bool(value and getattr(value, "url", False))
2020-07-10 21:28:09 +08:00
class TagInput(forms.TextInput):
"""
Dump tag queryset into tag list
"""
2023-01-11 19:11:31 -05:00
template_name = "widgets/tag.html"
2020-07-10 21:28:09 +08:00
def format_value(self, value):
2023-01-11 19:11:31 -05:00
if value == "" or value is None or len(value) == 0:
return ""
2020-07-10 21:28:09 +08:00
tag_list = []
try:
2023-01-11 19:11:31 -05:00
tag_list = [t["content"] for t in value]
2020-07-10 21:28:09 +08:00
except TypeError:
tag_list = [t.content for t in value]
# return ','.join(tag_list)
return tag_list
class Media:
2023-01-11 19:11:31 -05:00
css = {"all": ("lib/css/tag-input.css",)}
js = ("lib/js/tag-input.js",)
2020-07-10 21:28:09 +08:00
class TagField(forms.CharField):
"""
Split comma connected string into tag list
"""
2023-01-11 19:11:31 -05:00
2020-07-10 21:28:09 +08:00
widget = TagInput
2023-01-11 19:11:31 -05:00
2020-07-10 21:28:09 +08:00
def to_python(self, value):
value = super().to_python(value)
2020-07-10 22:49:08 +08:00
if not value:
return
2023-01-11 19:11:31 -05:00
return [t.strip() for t in value.split(",")]
2020-10-03 23:27:41 +02:00
class MultiSelect(forms.SelectMultiple):
2023-01-11 19:11:31 -05:00
template_name = "widgets/multi_select.html"
2020-10-03 23:27:41 +02:00
2020-10-04 16:16:50 +02:00
class Media:
css = {
2023-01-11 19:11:31 -05:00
"all": (
"https://cdn.jsdelivr.net/npm/multiple-select@1.5.2/dist/multiple-select.min.css",
)
2020-10-04 16:16:50 +02:00
}
2023-01-11 19:11:31 -05:00
js = (
"https://cdn.jsdelivr.net/npm/multiple-select@1.5.2/dist/multiple-select.min.js",
)
2020-10-04 16:16:50 +02:00
2020-10-03 23:27:41 +02:00
class HstoreField(forms.CharField):
widget = HstoreInput
2023-01-11 19:11:31 -05:00
2020-10-03 23:27:41 +02:00
def to_python(self, value):
if not value:
return None
2020-10-04 16:16:50 +02:00
# already in python types
if isinstance(value, list):
return value
2023-01-11 19:11:31 -05:00
pairs = json.loads("[" + value + "]")
2020-10-03 23:27:41 +02:00
return pairs
2021-02-12 19:23:23 +01:00
2021-02-15 21:27:50 +01:00
class DurationInput(forms.TextInput):
"""
HH:mm:ss input widget
"""
2023-01-11 19:11:31 -05:00
2021-02-15 21:27:50 +01:00
input_type = "time"
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
# context['widget']['type'] = self.input_type
2023-01-11 19:11:31 -05:00
context["widget"]["attrs"]["step"] = "1"
2021-02-15 21:27:50 +01:00
return context
def format_value(self, value):
"""
Given `value` is an integer in ms
"""
ms = value
if not ms:
return super().format_value(None)
x = ms // 1000
seconds = x % 60
x //= 60
if x == 0:
return super().format_value(f"00:00:{seconds:0>2}")
minutes = x % 60
x //= 60
if x == 0:
return super().format_value(f"00:{minutes:0>2}:{seconds:0>2}")
hours = x % 24
return super().format_value(f"{hours:0>2}:{minutes:0>2}:{seconds:0>2}")
class DurationField(forms.TimeField):
widget = DurationInput
2023-01-11 19:11:31 -05:00
2021-02-15 21:27:50 +01:00
def to_python(self, value):
# empty value
2023-01-11 19:11:31 -05:00
if value is None or value == "":
2021-02-15 21:27:50 +01:00
return
# if value is integer in ms
if isinstance(value, int):
return value
# if value is string in time format
2023-01-11 19:11:31 -05:00
h, m, s = value.split(":")
2021-02-15 21:27:50 +01:00
return (int(h) * 3600 + int(m) * 60 + int(s)) * 1000
2021-02-12 19:23:23 +01:00
#############################
# Form
#############################
2021-12-20 22:59:32 -05:00
VISIBILITY_CHOICES = [
(0, _("公开")),
(1, _("仅关注者")),
(2, _("仅自己")),
]
2021-02-12 19:23:23 +01:00
2021-12-20 22:59:32 -05:00
2023-01-11 19:11:31 -05:00
class MarkForm(forms.ModelForm):
2021-02-12 19:23:23 +01:00
id = forms.IntegerField(required=False, widget=forms.HiddenInput())
share_to_mastodon = forms.BooleanField(
2023-01-11 19:11:31 -05:00
label=_("分享到联邦网络"), initial=True, required=False
)
2021-02-12 19:23:23 +01:00
rating = forms.IntegerField(
2023-01-11 19:11:31 -05:00
label=_("评分"),
validators=[RatingValidator()],
widget=forms.HiddenInput(),
required=False,
)
2021-12-20 22:59:32 -05:00
visibility = forms.TypedChoiceField(
2021-02-12 19:23:23 +01:00
label=_("可见性"),
2021-12-20 22:59:32 -05:00
initial=0,
coerce=int,
choices=VISIBILITY_CHOICES,
2023-01-11 19:11:31 -05:00
widget=forms.RadioSelect,
2021-02-12 19:23:23 +01:00
)
tags = TagField(
required=False,
2023-01-11 19:11:31 -05:00
widget=TagInput(attrs={"placeholder": _("回车增加标签")}),
label=_("标签"),
2021-02-12 19:23:23 +01:00
)
text = forms.CharField(
required=False,
widget=forms.Textarea(
2023-01-11 19:11:31 -05:00
attrs={"placeholder": _("最多只能写360字哦~"), "maxlength": 360}
2021-02-12 19:23:23 +01:00
),
label=_("短评"),
)
2021-12-15 21:54:24 -05:00
2021-02-12 19:23:23 +01:00
class ReviewForm(forms.ModelForm):
2021-12-20 22:59:32 -05:00
title = forms.CharField(label=_("标题"))
content = MarkdownxFormField(label=_("正文 (Markdown)"))
2021-02-12 19:23:23 +01:00
share_to_mastodon = forms.BooleanField(
2023-01-11 19:11:31 -05:00
label=_("分享到联邦网络"), initial=True, required=False
)
2021-02-12 19:23:23 +01:00
id = forms.IntegerField(required=False, widget=forms.HiddenInput())
2021-12-20 22:59:32 -05:00
visibility = forms.TypedChoiceField(
2021-02-12 19:23:23 +01:00
label=_("可见性"),
2021-12-20 22:59:32 -05:00
initial=0,
coerce=int,
choices=VISIBILITY_CHOICES,
2023-01-11 19:11:31 -05:00
widget=forms.RadioSelect,
2021-02-12 19:23:23 +01:00
)