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

в 7:09, , рубрики: Без рубрики

Я работаю штатным программистом в Санкт-Петербургском Государственном Экономическом Университете и моей задачей с недавних пор является сопровождение деятельности отдела фото-видео производства (начинающее университетское телевидение). Как только начальник управления попросил всех зарегистрироваться в 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(длинна номера).

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>
+7{{ 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),]  
#Первое число - порядковый номер, второе - значение. В списке будут только значения.             

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

Автор: CLTanuki

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js