mirror of
https://github.com/SectorLabs/django-localized-fields.git
synced 2025-04-24 19:32:53 +03:00
193 lines
6.3 KiB
Python
193 lines
6.3 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 = []
|
|
|
|
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
|