import copy
import six
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from rest_framework.fields import ( # ModelField,
ChoiceField,
Field,
MultipleChoiceField,
empty,
)
__title__ = "fobi.contrib.apps.drf_integration.fields"
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
__copyright__ = "2016-2019 Artur Barseghyan"
__license__ = "GPL 2.0/LGPL 2.1"
__all__ = (
"ContentImageField",
"ContentMarkdownField",
"ContentRichTextField",
"ContentTextField",
"ContentVideoField",
"ModelChoiceField",
"ModelMultipleChoiceField",
"MultipleChoiceWithMaxField",
"NoneField",
)
# *****************************************************************************
# *****************************************************************************
# ************************** Additional DRF fields ****************************
# *****************************************************************************
# *****************************************************************************
# *****************************************************************************
# *************************** Traditional fields ******************************
[docs]class MultipleChoiceWithMaxField(MultipleChoiceField):
"""MultipleChoiceWithMaxField."""
default_error_messages = copy.copy(
MultipleChoiceField.default_error_messages
)
default_error_messages.update(
{
"max_choices": _("Max number of choices reached."),
}
)
def __init__(self, *args, **kwargs):
self.max_choices = kwargs.pop("max_choices", None)
super(MultipleChoiceWithMaxField, self).__init__(*args, **kwargs)
[docs] def to_internal_value(self, data):
if self.max_choices:
if len(data) > self.max_choices:
self.fail("max_choices")
return super(MultipleChoiceWithMaxField, self).to_internal_value(data)
class ModelChoiceFieldMixin(object):
"""Model choice field mixin."""
def get_choices(self):
"""Get choices."""
choices = []
if self.model_attr:
for _choice in self.queryset:
choices.append((_choice.pk, getattr(_choice, self.model_attr)))
else:
for _choice in self.queryset:
choices.append((_choice.pk, six.text_type(_choice)))
return choices
[docs]class ModelChoiceField(ChoiceField, ModelChoiceFieldMixin):
"""Model choice field."""
def __init__(self, *args, **kwargs):
self.queryset = kwargs.pop("queryset", None)
self.model_attr = kwargs.pop("model_attr", None)
choices = self.get_choices()
kwargs.update({"choices": choices})
super(ModelChoiceField, self).__init__(*args, **kwargs)
[docs]class ModelMultipleChoiceField(MultipleChoiceField, ModelChoiceFieldMixin):
"""Model choice field."""
def __init__(self, *args, **kwargs):
self.queryset = kwargs.pop("queryset", None)
self.model_attr = kwargs.pop("model_attr", None)
choices = self.get_choices()
kwargs.update({"choices": choices})
super(ModelMultipleChoiceField, self).__init__(*args, **kwargs)
# *****************************************************************************
# ************************* Presentational fields *****************************
# *****************************************************************************
[docs]class NoneField(Field):
"""NoneField."""
default_error_messages = {}
initial = ""
default_empty_html = ""
def __init__(self, **kwargs):
self.allow_blank = True
self.trim_whitespace = kwargs.pop("trim_whitespace", True)
self.raw_data = kwargs.pop("raw_data", {})
super(NoneField, self).__init__(**kwargs)
[docs] def run_validation(self, data=empty):
return ""
[docs] def to_internal_value(self, data):
# We're lenient with allowing basic numerics to be coerced into
# strings, but other types should fail. Eg. unclear if booleans
# should represent as `true` or `True`, and composites such as lists
# are likely user error.
_not_isinstance_str_int_float = not isinstance(
data, six.string_types + six.integer_types + (float,)
)
if isinstance(data, bool) or _not_isinstance_str_int_float:
self.fail("invalid")
value = six.text_type(data)
return value.strip() if self.trim_whitespace else value
[docs] def to_representation(self, value):
return mark_safe(six.text_type(value))
[docs]class ContentTextField(NoneField):
"""Content text field."""
[docs]class ContentRichTextField(NoneField):
"""Content rich text field."""
[docs]class ContentMarkdownField(NoneField):
"""Content markdown field."""
[docs]class ContentImageField(NoneField):
"""Content image field."""
[docs]class ContentVideoField(NoneField):
"""Content video field."""