2020-04-29 12:16:25 +03:00

196 lines
6.5 KiB
Python

from typing import List, Union
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.forms.widgets import FILE_INPUT_CONTRADICTION
from .value import (
LocalizedFileValue,
LocalizedIntegerValue,
LocalizedStringValue,
LocalizedValue,
)
from .widgets import (
AdminLocalizedIntegerFieldWidget,
LocalizedCharFieldWidget,
LocalizedFieldWidget,
LocalizedFileWidget,
)
class LocalizedFieldForm(forms.MultiValueField):
"""Form for a localized field, allows editing the field in multiple
languages."""
widget = LocalizedFieldWidget
field_class = forms.fields.CharField
value_class = LocalizedValue
def __init__(
self, *args, required: Union[bool, List[str]] = False, **kwargs
):
"""Initializes a new instance of :see:LocalizedFieldForm."""
fields = []
# Do not print initial value in html in the form of a hidden input. This will result in loss of information
kwargs["show_hidden_initial"] = False
for lang_code, _ in settings.LANGUAGES:
field_options = dict(
required=required
if type(required) is bool
else (lang_code in required),
label=lang_code,
)
fields.append(self.field_class(**field_options))
super(LocalizedFieldForm, self).__init__(
fields,
required=required if type(required) is bool else True,
require_all_fields=False,
*args,
**kwargs
)
# set 'required' attribute for each widget separately
for field, widget in zip(self.fields, self.widget.widgets):
widget.is_required = field.required
def compress(self, value: List[str]) -> LocalizedValue:
"""Compresses the values from individual fields into a single
:see:LocalizedValue instance.
Arguments:
value:
The values from all the widgets.
Returns:
A :see:LocalizedValue containing all
the value in several languages.
"""
localized_value = self.value_class()
for (lang_code, _), value in zip(settings.LANGUAGES, value):
localized_value.set(lang_code, value)
return localized_value
class LocalizedCharFieldForm(LocalizedFieldForm):
"""Form for a localized char field, allows editing the field in multiple
languages."""
widget = LocalizedCharFieldWidget
value_class = LocalizedStringValue
class LocalizedTextFieldForm(LocalizedFieldForm):
"""Form for a localized text field, allows editing the field in multiple
languages."""
value_class = LocalizedStringValue
class LocalizedIntegerFieldForm(LocalizedFieldForm):
"""Form for a localized integer field, allows editing the field in multiple
languages."""
widget = AdminLocalizedIntegerFieldWidget
value_class = LocalizedIntegerValue
class LocalizedFileFieldForm(LocalizedFieldForm, forms.FileField):
"""Form for a localized file field, allows editing the field in multiple
languages."""
widget = LocalizedFileWidget
field_class = forms.fields.FileField
value_class = LocalizedFileValue
def clean(self, value, initial=None):
"""Most part of this method is a copy of
django.forms.MultiValueField.clean, with the exception of initial value
handling (this need for correct processing FileField's).
All original comments saved.
"""
if initial is None:
initial = [None for x in range(0, len(value))]
else:
if not isinstance(initial, list):
initial = self.widget.decompress(initial)
clean_data = []
errors = []
if not value or isinstance(value, (list, tuple)):
is_empty = [v for v in value if v not in self.empty_values]
if (not value or not is_empty) and (not initial or not is_empty):
if self.required:
raise ValidationError(
self.error_messages["required"], code="required"
)
else:
raise ValidationError(
self.error_messages["invalid"], code="invalid"
)
for i, field in enumerate(self.fields):
try:
field_value = value[i]
except IndexError:
field_value = None
try:
field_initial = initial[i]
except IndexError:
field_initial = None
if (
field_value in self.empty_values
and field_initial in self.empty_values
):
if self.require_all_fields:
# Raise a 'required' error if the MultiValueField is
# required and any field is empty.
if self.required:
raise ValidationError(
self.error_messages["required"], code="required"
)
elif field.required:
# Otherwise, add an 'incomplete' error to the list of
# collected errors and skip field cleaning, if a required
# field is empty.
if field.error_messages["incomplete"] not in errors:
errors.append(field.error_messages["incomplete"])
continue
try:
clean_data.append(field.clean(field_value, field_initial))
except ValidationError as e:
# Collect all validation errors in a single list, which we'll
# raise at the end of clean(), rather than raising a single
# exception for the first error we encounter. Skip duplicates.
errors.extend(m for m in e.error_list if m not in errors)
if errors:
raise ValidationError(errors)
out = self.compress(clean_data)
self.validate(out)
self.run_validators(out)
return out
def bound_data(self, data, initial):
bound_data = []
if initial is None:
initial = [None for x in range(0, len(data))]
else:
if not isinstance(initial, list):
initial = self.widget.decompress(initial)
for d, i in zip(data, initial):
if d in (None, FILE_INPUT_CONTRADICTION):
bound_data.append(i)
else:
bound_data.append(d)
return bound_data