Поле MultiValueField

Класс MultiValueField применяется для создания полей формы, которые представляют собой комбинацию нескольких подполей. Это поле используется, когда необходимо собрать данные из нескольких полей ввода и обработать их как единое составное значение. Поле принимает список дочерних полей, каждый из которых отвечает за свою часть данных.

Синтаксис

pythonfrom django import forms

class MultiValueField(fields, widget=None, **kwargs)

Основные параметры конструктора:

Общее описание

MultiValueField является абстрактным базовым классом, который требует переопределения метода compress. Этот метод отвечает за преобразование списка валидированных значений из дочерних полей в единое составное значение. Поле автоматически обрабатывает валидацию каждого дочернего поля и собирает ошибки в общий список.

Пример

Рассмотрим создание составного поля для ввода телефонного номера, состоящего из кода страны и основного номера:

pythonfrom django import forms
from django.core.exceptions import ValidationError

class PhoneNumberField(forms.MultiValueField):
  def __init__(self, **kwargs):
    fields = (
      forms.CharField(max_length=5, label='Country code'),
      forms.CharField(max_length=15, label='Phone number'),
    )
    widget = forms.MultiWidget(
      widgets=(
        forms.TextInput(attrs={'placeholder': '+7'}),
        forms.TextInput(attrs={'placeholder': '123-45-67'}),
      )
    )
    super().__init__(fields=fields, widget=widget, **kwargs)

  def compress(self, data_list):
    if data_list:
      country_code = data_list[0]
      phone_number = data_list[1]
      if not phone_number:
        raise ValidationError('Phone number is required')
      return f'{country_code}{phone_number}'
    return None

Теперь используем это поле в форме:

pythonfrom django import forms

class ContactForm(forms.Form):
  name = forms.CharField(max_length=100)
  phone = PhoneNumberField()

form = ContactForm(data={
  'name': 'John Doe',
  'phone_0': '+7',
  'phone_1': '123-45-67',
})
if form.is_valid():
  res = form.cleaned_data
  print(res)

Результат выполнения кода:

python{'name': 'John Doe', 'phone': '+7123-45-67'}

Пример

Создадим составное поле для ввода диапазона цен с минимальным и максимальным значениями:

pythonfrom django import forms
from django.core.exceptions import ValidationError

class PriceRangeField(forms.MultiValueField):
  def __init__(self, **kwargs):
    fields = (
      forms.DecimalField(max_value=1000000, label='Min price'),
      forms.DecimalField(max_value=1000000, label='Max price'),
    )
    widget = forms.MultiWidget(
      widgets=(
        forms.NumberInput(attrs={'placeholder': 'from'}),
        forms.NumberInput(attrs={'placeholder': 'to'}),
      )
    )
    super().__init__(fields=fields, widget=widget, **kwargs)

  def compress(self, data_list):
    if data_list:
      min_price = data_list[0]
      max_price = data_list[1]
      if min_price is not None and max_price is not None:
        if min_price > max_price:
          raise ValidationError('Min price cannot be greater than max price')
      return {'min': min_price, 'max': max_price}
    return None

Использование поля в форме:

pythonfrom django import forms

class ProductFilterForm(forms.Form):
  category = forms.ChoiceField(choices=[('electronics', 'Electronics'), ('books', 'Books')])
  price_range = PriceRangeField(required=False)

form = ProductFilterForm(data={
  'category': 'electronics',
  'price_range_0': '100',
  'price_range_1': '500',
})
if form.is_valid():
  res = form.cleaned_data
  print(res)

Результат выполнения кода:

python{'category': 'electronics', 'price_range': {'min': 100, 'max': 500}}

Пример

Рассмотрим обработку ошибок валидации в составном поле для ввода времени с часами и минутами:

pythonfrom django import forms
from django.core.exceptions import ValidationError

class TimeField(forms.MultiValueField):
  def __init__(self, **kwargs):
    fields = (
      forms.IntegerField(min_value=0, max_value=23, label='Hour'),
      forms.IntegerField(min_value=0, max_value=59, label='Minute'),
    )
    widget = forms.MultiWidget(
      widgets=(
        forms.NumberInput(attrs={'placeholder': 'HH'}),
        forms.NumberInput(attrs={'placeholder': 'MM'}),
      )
    )
    super().__init__(fields=fields, widget=widget, **kwargs)

  def compress(self, data_list):
    if data_list:
      hour = data_list[0]
      minute = data_list[1]
      if hour is None or minute is None:
        raise ValidationError('Both hour and minute are required')
      return f'{hour:02d}:{minute:02d}'
    return None

Проверим форму с некорректными данными:

pythonfrom django import forms

class ScheduleForm(forms.Form):
  event_name = forms.CharField(max_length=200)
  start_time = TimeField()

form = ScheduleForm(data={
  'event_name': 'Meeting',
  'start_time_0': '25',
  'start_time_1': '70',
})
if form.is_valid():
  res = form.cleaned_data
  print(res)
else:
  res = form.errors
  print(res)

Результат выполнения кода:

python{'start_time': ['Ensure this value is less than or equal to 23.', 'Ensure this value is less than or equal to 59.']}

Смотрите также

  • поле SplitDateTimeField,
    которое является встроенной реализацией составного поля для даты и времени
  • класс MultiWidget,
    который используется для отображения нескольких виджетов в одном поле
  • поле ComboField,
    которое объединяет несколько валидаторов для одного поля
  • метод clean,
    который используется для валидации нескольких полей формы