Виджет MultiWidget

Виджет MultiWidget применяется в формах Django для создания составных полей ввода, которые состоят из нескольких независимых подвиджетов. Этот класс полезен, когда необходимо объединить несколько связанных данных в одном поле, например, дату и время, или диапазон чисел. Виджет наследует от Widget и переопределяет методы для рендеринга и обработки данных из нескольких источников.

Синтаксис

pythonfrom django.forms import MultiWidget

class CustomMultiWidget(MultiWidget):
  def __init__(self, attrs=None):
    widgets = [
      SomeWidget(attrs=attrs),
      AnotherWidget(attrs=attrs),
    ]
    super().__init__(widgets, attrs=attrs)

  def decompress(self, value):
    """
    Разбивает составное значение на список значений для каждого виджета.
    """
    if value:
      return [value.part1, value.part2]
    return [None, None]

Основные компоненты виджета:

Пример

Создадим виджет для ввода диапазона цен, состоящий из двух полей: минимальная и максимальная цена:

pythonfrom django.forms import MultiWidget, NumberInput

class PriceRangeWidget(MultiWidget):
  def __init__(self, attrs=None):
    widgets = [
      NumberInput(attrs={'placeholder': 'Min', **attrs}),
      NumberInput(attrs={'placeholder': 'Max', **attrs}),
    ]
    super().__init__(widgets, attrs=attrs)

  def decompress(self, value):
    if value:
      return [value.min_price, value.max_price]
    return [None, None]

Пример

Используем созданный виджет в форме для ввода диапазона цен:

pythonfrom django import forms
from django.core.validators import MinValueValidator

class PriceRangeForm(forms.Form):
  price_range = forms.CharField(
    widget=PriceRangeWidget(attrs={'class': 'price-input'}),
    required=False,
    label='Price range'
  )
  
  def clean_price_range(self):
    data = self.cleaned_data['price_range']
    if data:
      min_val, max_val = data.split(',')
      min_val = int(min_val)
      max_val = int(max_val)
      if min_val > max_val:
        raise forms.ValidationError("Min price cannot be greater than max price")
      return {'min': min_val, 'max': max_val}
    return None

Результат рендеринга формы:

python<form method="post">
  <div class="field-wrapper">
    <label for="id_price_range">Price range:</label>
    <input type="number" name="price_range_0" placeholder="Min" class="price-input" id="id_price_range_0">
    <input type="number" name="price_range_1" placeholder="Max" class="price-input" id="id_price_range_1">
  </div>
  <button type="submit">Submit</button>
</form>

Пример

Создадим виджет для ввода времени в формате "часы:минуты" с использованием выпадающих списков:

pythonfrom django.forms import MultiWidget, Select

class TimePickerWidget(MultiWidget):
  def __init__(self, attrs=None):
    hours = [(str(i), f"{i:02d}") for i in range(24)]
    minutes = [(str(i), f"{i:02d}") for i in range(60)]
    widgets = [
      Select(choices=hours, attrs=attrs),
      Select(choices=minutes, attrs=attrs),
    ]
    super().__init__(widgets, attrs=attrs)

  def decompress(self, value):
    if value:
      return value.split(':')
    return [None, None]

Используем виджет для выбора времени в форме:

pythonfrom django import forms

class AppointmentForm(forms.Form):
  appointment_time = forms.CharField(
    widget=TimePickerWidget(attrs={'class': 'time-select'}),
    label='Appointment time',
    help_text='Select hours and minutes'
  )
  
  def clean_appointment_time(self):
    data = self.cleaned_data['appointment_time']
    if data:
      hours, minutes = data.split(',')
      return f"{int(hours):02d}:{int(minutes):02d}"
    return None

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

python"Appointment time: 14:30"

Пример

Создадим составной виджет для ввода полного имени, состоящий из полей для имени и фамилии:

pythonfrom django.forms import MultiWidget, TextInput

class FullNameWidget(MultiWidget):
  def __init__(self, attrs=None):
    widgets = [
      TextInput(attrs={'placeholder': 'First name', **attrs}),
      TextInput(attrs={'placeholder': 'Last name', **attrs}),
    ]
    super().__init__(widgets, attrs=attrs)

  def decompress(self, value):
    if value:
      return value.split(' ')
    return ['', '']

Используем виджет в форме для регистрации пользователя:

pythonfrom django import forms

class UserRegistrationForm(forms.Form):
  full_name = forms.CharField(
    widget=FullNameWidget(attrs={'class': 'name-field'}),
    label='Full name',
    required=True
  )
  
  def clean_full_name(self):
    data = self.cleaned_data['full_name']
    if data:
      first, last = data.split(',')
      return f"{first.strip()} {last.strip()}"
    return None

Результат обработки данных формы:

python"Full name: John Doe"

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

  • виджет SplitDateTimeWidget,
    который разделяет ввод даты и времени на два отдельных поля
  • виджет SelectDateWidget,
    который использует выпадающие списки для выбора даты
  • поле MultiValueField,
    которое используется вместе с MultiWidget для валидации составных данных
  • виджет TextInput,
    стандартный виджет для ввода текста, используемый в составных виджетах