from django import forms from markdownx.fields import MarkdownxFormField import django.contrib.postgres.forms as postgres from django.utils import formats from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ import json class KeyValueInput(forms.Widget): """ Input widget for Json field """ template_name = 'widgets/hstore.html' def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) data = None 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 [] return context class Media: js = ('js/key_value_input.js',) class HstoreInput(forms.Widget): """ Input widget for Hstore field """ template_name = 'widgets/hstore.html' def format_value(self, value): """ Return a value as it should appear when rendered in a template. """ if value == '' or value is None: return None if self.is_localized: return formats.localize_input(value) # do not return str return value class Media: js = ('js/key_value_input.js',) class JSONField(forms.fields.JSONField): widget = KeyValueInput def to_python(self, value): if not value: return None j = {} if isinstance(value, dict): j = value else: pairs = json.loads('[' + value + ']') if isinstance(pairs, dict): j = pairs else: # list or tuple for pair in pairs: j = {**j, **pair} return super().to_python(j) 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. if isinstance(value, str) and value.lower() in ('false', '0'): value = False else: value = bool(value) return value class RatingValidator: """ empty value is not validated """ def __call__(self, value): if not isinstance(value, int): raise ValidationError( _('%(value)s is not an integer'), params={'value': value}, ) if not str(value) in [str(i) for i in range(0, 11)]: raise ValidationError( _('%(value)s is not an integer in range 1-10'), params={'value': value}, ) class PreviewImageInput(forms.FileInput): template_name = 'widgets/image.html' def format_value(self, value): """ Return the file object if it has a defined url attribute. """ if self.is_initial(value): if value.url: return value.url else: return def is_initial(self, value): """ Return whether value is considered to be initial value. """ return bool(value and getattr(value, 'url', False)) class TagInput(forms.TextInput): """ Dump tag queryset into tag list """ template_name = 'widgets/tag.html' def format_value(self, value): if value == '' or value is None or len(value) == 0: return '' tag_list = [] try: tag_list = [t['content'] for t in value] except TypeError: tag_list = [t.content for t in value] # return ','.join(tag_list) return tag_list class Media: css = { 'all': ('lib/css/tag-input.css',) } js = ('lib/js/tag-input.js',) class TagField(forms.CharField): """ Split comma connected string into tag list """ widget = TagInput def to_python(self, value): value = super().to_python(value) if not value: return return [t.strip() for t in value.split(',')] class MultiSelect(forms.SelectMultiple): template_name = 'widgets/multi_select.html' class Media: css = { 'all': ('https://cdn.jsdelivr.net/npm/multiple-select@1.5.2/dist/multiple-select.min.css',) } js = ('https://cdn.jsdelivr.net/npm/multiple-select@1.5.2/dist/multiple-select.min.js',) class HstoreField(forms.CharField): widget = HstoreInput def to_python(self, value): if not value: return None # already in python types if isinstance(value, list): return value pairs = json.loads('[' + value + ']') return pairs class DurationInput(forms.TextInput): """ HH:mm:ss input widget """ input_type = "time" def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) # context['widget']['type'] = self.input_type context['widget']['attrs']['step'] = "1" 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 def to_python(self, value): # empty value if value is None or value == '': return # if value is integer in ms if isinstance(value, int): return value # if value is string in time format h, m, s = value.split(':') return (int(h) * 3600 + int(m) * 60 + int(s)) * 1000 ############################# # Form ############################# VISIBILITY_CHOICES = [ (0, _("公开")), (1, _("仅关注者")), (2, _("仅自己")), ] class MarkForm(forms.ModelForm): id = forms.IntegerField(required=False, widget=forms.HiddenInput()) share_to_mastodon = forms.BooleanField( label=_("分享到联邦网络"), initial=True, required=False) rating = forms.IntegerField( label=_("评分"), validators=[RatingValidator()], widget=forms.HiddenInput(), required=False) visibility = forms.TypedChoiceField( label=_("可见性"), initial=0, coerce=int, choices=VISIBILITY_CHOICES, widget=forms.RadioSelect ) tags = TagField( required=False, widget=TagInput(attrs={'placeholder': _("回车增加标签")}), label=_("标签") ) text = forms.CharField( required=False, widget=forms.Textarea( attrs={ "placeholder": _("最多只能写360字哦~"), "maxlength": 360 } ), label=_("短评"), ) class ReviewForm(forms.ModelForm): title = forms.CharField(label=_("标题")) content = MarkdownxFormField(label=_("正文 (Markdown)")) share_to_mastodon = forms.BooleanField( label=_("分享到联邦网络"), initial=True, required=False) id = forms.IntegerField(required=False, widget=forms.HiddenInput()) visibility = forms.TypedChoiceField( label=_("可见性"), initial=0, coerce=int, choices=VISIBILITY_CHOICES, widget=forms.RadioSelect )