Несколько полезных полей Django

Я работаю штатным программистом в Санкт-Петербургском Государственном Экономическом Университете и моей задачей с недавних пор является сопровождение деятельности отдела фото-видео производства (начинающее университетское телевидение). Как только начальник управления попросил всех зарегистрироваться в Bitrix24 с целью автоматизировать отчёты, я подумал об автоматизации входящих задач (на самом деле всё немного глубже и целью была дисциплина, но к посту не относится).

Собственно, первом делом я взялся за форму заявки на фото-видео съёмку, которую, недолго думая, набросал в Django. К этому моменту мои познания фрэймворка ограничивались несколькими представлениями для вывода списков на внутреннем сервере отдела. Основной сложность в форме стали внезапно поля — хотелось одновременно и красоту навести и от ошибок ввода пользователя уберечь(ся). Особенно интересовали меня два поля — поле ввода телефона и выбора времени. О них и речь.

Начну с поля для ввода телефона. Сначала я обратился к Хабру и нашёл некий пост, однако увиденное показалось мне слишком громоздким для моей цели (ведь мне надо было просто передать телефон оператору). Я понял, что использование CharField не приведёт ни к одной из моих целей и, пошуршав документацией Django, я нашёл там MultiValueField и MultiWidget в дополнение (сразу хочу сказать, что использование только MultiValueField порождает обычную строку для ввода текста, что никакого смысла не имеет). Через несколько минут был написан код под катом.


1) Для начала импортируем необходимое.
from django.forms import MultiValueField, CharField, ChoiceField, MultiWidget, TextInput, Select


2) Затем определяем PhoneWidget, базовым классом для которого будет MultiWidget.
class PhoneWidget(MultiWidget):
    def __init__(self, code_length=3, num_length=7, attrs=None):
        widgets = [TextInput(attrs={'size': code_length, 'maxlength': code_length}),
                   TextInput(attrs={'size': num_length, 'maxlength': num_length})]
        super(PhoneWidget, self).__init__(widgets, attrs)

    def decompress(self, value):
        if value:
            return [value.code, value.number]
        else:
            return ['', '']

Немного пояснений:
Длина кода города в Санкт-Петербурге и мобильных всея Руси — 3 символа, а самого телефона — 7. Но и тот и другой параметр могут меняться, поэтому они и были указаны в конструкторе с дефолтными значениями, подходящими для меня.
Далее идёт определение 2х самих виджетов (думаю, для понимания можно назвать их представлениями полей) с ограничениями длинны поля ввода (для красоты) и количества символов (для защиты от ошибок) параметрами code_length(длина кода) и num_length(длина номера).

Красоты феншуя ради я добавил ещё метод format_output()
def format_output(self, rendered_widgets):
        return '+7' + '(' + rendered_widgets[0] + ') - ' + rendered_widgets[1]

дабы получить на выходе красивое форматирование в html.

3) Определяем само поле базовым классом для которого будет MultiValueField.
class PhoneField(MultiValueField):
    def __init__(self, code_length, num_length, *args, **kwargs):
        list_fields = [CharField(),
                       CharField()]
        super(PhoneField, self).__init__(list_fields, widget=PhoneWidget(code_length, num_length), *args, **kwargs)

    def compress(self, values):
        return '+7' + values[0] + values[1]  #Собственно, стандартизация строки номера эстетики ради

Немного пояснений:
В конструкторе содержатся те же параметры для передачи конструктору виджета. Иначе, как я уже писал, мы получим обычную строку для ввода текста.

4) В итоге в форме мы указываем поле
p_num = PhoneField()  
#При желании\необходимости можно вызвать с параметрами: PhoneField(code_length=some_value, num_length=some_value)

и, добавив в шаблон
{{ form.p_num.errors }}
<label for="phone_num">Номер телефона:</label>
</br>
{{ form.p_num }}

мы получаем два красивых поля длинной 3 и 7 (или сколько указано) символов и международным кодом впереди для его стандартизации и подсказки полльзователю.

По аналогии были сделаны виджет и поле для ввода времени:
class TimeWidget(MultiWidget):
    def __init__(self, h_choices, m_choices, attrs=None):
        widgets = [Select(choices=h_choices),
                   Select(choices=m_choices)]
        super(TimeWidget, self).__init__(widgets, attrs)

    def decompress(self, value):
        if value:
            return [value.hours, value.minutes]
        else:
            return ['', '']


class TimeField(MultiValueField):
    def __init__(self, h_choices, m_choices, *args, **kwargs):
        list_fields = [ChoiceField(),
                       ChoiceField()]
        super(TimeField, self).__init__(list_fields, widget=TimeWidget(h_choices, m_choices),*args, **kwargs)

    def compress(self, values):
        return return values[0] + ':' + values[1]  #Стандартизация для приведения впоследствии к объекту datetime

Для защиты пользователя от указания времени вне рабочего дня, а также вне разумного лимита (24 и 60) я сделал 2 списка с выбором параметром.

Вызов:
time = TimeField(h_choices=HOURS_CHOICES, m_choices=MINUTES_CHOICES)

И сами параметры (в соответствии с документацией Django должны быть списком кортежей и определяться вне класса формы):
HOURS_CHOICES = [(str(x), x) for x in range(9, 21)]  
#Даю на выбор лишь разумное рабочее время, чтобы не ворчали операторы
MINUTES_CHOICES = [(1, 0), (2, 10), (3, 20), (4, 30), (5, 40), (6, 50),]  
#Первое число - порядковый номер, второе - значение. В списке будут только значения.             



Осталось только понять, как между полями вставлять некие символьные разделители (например, ':' между часами и минутами), но функциональной нагрузки это не несёт. Впрочем, я постараюсь решить и эту задачу и дополню пост найденным решением, а также другими примерами полей.

UPD: описан метод format_output() для получения всяких скобочек рюшечек опрятного форматирования.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 15

    +2
    Осталось только понять, как между полями вставлять некие символьные разделители (например, ':' между часами и минутами)

    Переопределить метод MultiWidget.render()?
      0
      сразу хочу сказать, что использование только MultiValueField порождает обычную строку для ввода текста, что никакого смысла не имеет
      Это по определению очевидно, т.к. MultiValueField это поле джанго-формы, а инпуты «порождает» то, что заявлено как widget для этого поля (можно и на ходу задать).
      Длинна
      Длина
      Осталось только понять, как между полями вставлять некие символьные разделители
      Если речь про разделители между инпутами, то, очевидно, это в render виджета делается, как и другие дела с их расположением и видом самого виджета. Про это всё в доке есть. А в самих исходниках джанги несколько примеров использования (как раз вроде в том числе для времени и даты, не помню, ни разу не использовал эти поля/виджеты). Вообще в принципе примеры уровня джанговской доки у вас.
        0
        За длину спасибо — поправил. Писалось ночью, но это не оправдание=)
        Про рендер посмотрю и дополню пост.
        Если «примеры уровня джанговской доки» — положительное качество, то я старался. Если нет, то я тоже старался, но не получилось=)
        +1
        Мне почему-то кажется, что пользователи мобильных устройств будут поминать разработчиков таких композитных полей по матушке.
          0
          К сожалению, опыт показывает, что кегль хелп текста должен быть > 9000 для того, чтобы призвать хотя бы половину пользователей правильно вводить сложные наборы в текстовые поля=)
            0
            И не надо пользователей призывать. Нехай вводят так, как им удобно. А нормализовывать — это наше дело, программерское.
            Кроме того, вроде бы существуют инпуты соответствующего типа, специально для телефонов.
          0
          Осталось только понять, как между полями вставлять некие символьные разделители (например, ':' между часами и минутами)


          В AdminTimeWidget, если я не ошибаюсь, это реализовано с помощью js. Можете подчерпнуть идей оттуда.
            0
            Для хранения телефонных номеров советую попробовать библиотеку django-phonenumber-field.
            В её составе есть FormField и ModelField, так же из коробки получаем валидацию номеров для кучи стран и форматирование в национальном и международном формате.
              0
              Разделители вообще можно через CSS добавить :before & :after селекторами.
                0
                Как вариант, если ничто другое не поможет.
                +1
                А еще можно разрешить своим пользователям вводить телефон как душе заблагорассудится, и все равно получить на выходе красивый стандартизованный телефон. Вот так.
                  0
                  «После стандартизации от введенных данных ничего не осталось.»
                  Ну и, конечно же, необходимость подгружать туда файлик руками… Я же хочу сделать так, чтобы до добавления нового функционала спокойно заниматься иными задачами, коих, кстати, не так уж мало.
                    0
                    «После стандартизации от введенных данных ничего не осталось» — так происходит, если ввели совсем уж мусор вместо телефона.

                    Руками файлики подгружать совсем не обязательно, есть REST API.
                      +1
                      Тогда вся загвоздка в разделе «Стоимость» и в гарантиях жизни сервиса. Впрочем, желаю удачи разработчикам.
                        0
                        Спасибо за пожелание! :-)

                        Насчет гарантий жизни — это стратегический проект для нашей компании, и мы не планируем его закрывать. Стоимость для небольших проектов будет минимальной (или вообще нулевой), ну а для крупных сайтов уж точно не станет существенной статьей расхода.

                Only users with full accounts can post comments. Log in, please.